美文网首页
算法题常用Java方法与技巧

算法题常用Java方法与技巧

作者: 桃桃沙弥 | 来源:发表于2021-07-29 23:06 被阅读0次

主要介绍在leetcode中经常会使用到的一些Java函数,及其复杂度分析。因为考虑到可能会需要白板编程,没有IDE提示,所以还是把函数名、用法都记忆一下吧。

输入输出

笔试的时候可能会使用ACM模式,需要自己处理输入输出。LC不需要。以防万一,还是学习一下。

  • 输入

大部分常用的数据类型可以使用next+数据类型的方法来进行读取。

        Scanner reader = new Scanner(System.in);
        int a = reader.nextInt();
        long b = reader.nextLong();
        String c = reader.next();

对于char类型比较特殊,一般来说有两种方法。第一种是把读取的byte转成了char,第二种是选取了string的第一位。

        char c1 = (char)System.in.read();
        char c2 = reader.next().charAt(0);

以上这些方法适用于单独调用读取单个或者几个数据。若有多个数据在同一行。推荐方式为,确认数量后循环调用。

        Scanner reader = new Scanner(System.in);
        int num = reader.nextInt();
        int[] nums = new int[num];
        for (int i = 0; i < num; ++i) {
            nums[i] = reader.nextInt();
        }

        Scanner reader = new Scanner(System.in);
        int num = reader.nextInt();
        char[] cs = new char[num];
        for (int i = 0; i < num; ++i) {
            cs[i] = reader.next().charAt(0);
        }

        for (int i = 0; i < num; ++i) {
            System.out.println(cs[i]);
        }

        Scanner reader = new Scanner(System.in);
        int num = reader.nextInt();
        String[] cs = new String[num];
        for (int i = 0; i < num; ++i) {
            cs[i] = reader.next();
        }

        for (int i = 0; i < num; ++i) {
            System.out.println(cs[i]);
        }

读取整一行数据使用nextLine(),配合split()使用可以也可以得到string数组。

  • 输入相关的易错点

输入:
1
asd
然后分别使用nextInt()和nextLine()读取,结果读不到后面的asd。

分析:
nextInt(): it only reads the int value, nextInt() places the cursor in the same line after reading the input.

只读取整数类型数据, nextInt()在读取完输入后把光标放在读取数据的同一行,该数据的后面。

next(): read the input only till the space. It can't read two words separated by space. Also, next() places thecursor in the same line after reading the input.

只读取到空格,不能读取被空格分开的两个单词(也就是不能读取空格),并且在读取完后把光标放在读取数据的同一行,该数据的后面。(同上)

nextLine(): reads input including space between the words (that is, it reads till the end of line \n). Once the input is read, nextLine() positions thecursor in the next line.

读取整行的数据包括单词间的空格,到回车结束(也就是从开始读一整行包括回车),读取结束后,光标放在下一行开头。

所以出现上面情况原因:nextInt()只读取了数值1,剩下"\n"还没有读取,并将光标放在本行中1后面。接着nextLine()会读取"\n",并结束本次读取。

解决方案:
可以在nextInt()(或next())和nextLine()之间添加一个nextLine()用来吸收掉空格或回车

  • 输出
    常用的格式化输出方法:
        int x = 5;
        double y = 3.141592;

        System.out.println("x = " + x + ", y = " + y);

        System.out.printf("x = %d, y = %f\n", x, y);

        System.out.format("x = %d, y = %f\n", x, y);

有一次笔试要输出小数点后两位+百分号的形式,还卡了挺久的。

        double d = 1.234;
        System.out.println(String.format("the number is %.2f%%", d));

中间使用了String的静态方法format。为什么要写两个%%,由于%符号用作格式说明符的前缀,因此,如果要将%符号显示为输出字符串的一部分,则需要对其进行转义。

要转义百分号(%),您需要像写两次%%。它将%在printf()等格式化输出方法打印出一个符号。

格式化输出中的占位符见下图:


又是某次amazon oa,要四舍五入...如果不想手写,最好的做法是使用bigDecimal

double   f   =   111231.5585;
BigDecimal   b   =   new   BigDecimal(f);
double   f1   =   b.setScale(2, RoundingMode.HALF_UP).doubleValue();  //HALF_UP是五入

HashMap

  • getOrDefault
Map<String, List<String>>  test = new HashMap<String, List<String>>();
List<String> children = test.getOrDefault(parentName, new ArrayList<String>());

一个小坑:
由于java是传值,所以在下例中,childs是引用,对List进行修改,是不需要重新用put写入的,但是由于有default新建一个ArrayList的情况存在,所以需要put,否则修改会丢失。

        List<String> childs = edges.getOrDefault(parentName, new ArrayList<String>());
        childs.add(childName);
        edges.put(parentName, childs); //对于default的情况是必需的
  • 遍历
  1. 使用entrySet(for-each或者iterator)

        Map<Integer, Integer> testMap = new HashMap<>();
        testMap.put(1, 100);
        testMap.put(2, 200);
        testMap.put(3, 300);
        //使用for-each
        for (Map.Entry<Integer, Integer> entry : testMap.entrySet()) {
            int key = entry.getKey();
            int val = entry.getValue();
        }
       //使用iterator,太绕了,别用了吧
        Iterator<Map.Entry<Integer, Integer>> itr = testMap.entrySet().iterator();
        while (itr.hasNext()) {
            Map.Entry<Integer, Integer> entry = itr.next();
            int key = entry.getKey();
            int val = entry.getValue();
        }
  1. 只遍历key或者value可以使用keySet或者values
        for (Integer key : testMap.keySet()) {
            System.out.println(key);
        }
        for (Integer val : testMap.values()) {
            System.out.println(val);
        }
  • 包含
    分key和value
        System.out.println(testMap.containsKey("1"));
        System.out.println(testMap.containsValue("100"));
  • 取出value
    values方法,返回 HashMap 中所有 value 值所组成的 collection view(集合视图)。
//比如有些算法题要从hashMap中得到List<List<String>>
Map<String, String> map = new HashMap<>();
return new ArrayList<>(map.values());

HashSet

由ArrayList创建HashSet

Set<String> distSet = new HashSet<>(wordList);

将一个hashSet的内容添加到另一个hashSet中

 visited.addAll(subVisited);

Set转List,使用ArrayList的构造方法

List<String> ls=new ArrayList(myset);

TreeMap

treeMap和hashMap的最大差别是,treeMap中的元素(key)是可以排序的。默认按照key升序排列,可以自定义排序。The Map is sorted according to the natural sort method for the key Class, or by the Comparator provided at map creation time, that will depend on which constructor used.

  1. Snapshot Array 就考了一道用TreeMap的数据结构题
//Contructor
//由hashMap生成treeMap
TreeMap<String, Integer> treeMap = new TreeMap<String, Integer>(map);
//自定义comparator
class AccordingMarks implements Comparator<Student> {
    public int compare(Student s1, Student s2)
    {
        return s1.getMarks().compareTo(s2.getMarks());
    }
}
TreeMap<Student, Integer> map
            = new TreeMap<>(new AccordingMarks());

//重要方法
//This method returns a key-value mapping associated with the least key greater than or equal to the given key, or null if there is no such key.
//>=
Map.Entry<K,V> ceilingEntry(K key)

//This method returns the least key greater than or equal to the given key, or null if there is no such key.
K ceilingKey(K key)

//This method returns a key-value mapping associated with the greatest key less than or equal to the given key, or null if there is no such key.
//<=
Map.Entry<K,V> floorEntry(K key)

//
K floorKey(K key)

//>
//This method returns the returns a key-value mapping associated with the least key strictly greater than the given key, or null if there is no such key.
Map.Entry<K,V> higherEntry(K key)

K higherKey(K key)

//<
Map.Entry<K,V> lowerEntry(K key)

K lowerKey(K key)

//This method returns a key-value mapping associated with the least key in this map, or null if the map is empty.
public Map.Entry<K,V> firstEntry()

public K firstKey()

public Map.Entry<K,V> lastEntry()
public K lastKey()
//>=, <
SortedMap<K,V> subMap(K fromKey, K toKey)
//>=
SortedMap<K,V> tailMap(K fromKey)


//其他方法和hashmap的差不多
V get(Object key)
V put(K key, V value)
int size()
boolean containsKey(Object key)
boolean containsValue(Object value)
Set<Map.Entry<K,V>> entrySet()
Set<K> keySet()
V remove(Object key)
Collection<V> values()

ArrayList

public boolean add(E e)
public void add(int index,E element)
public void add(E element) //默认加到最后
public boolean addAll(Collection<? extends E> c)
public boolean addAll( int index,Collection<? extends E e> c )  
public E get(int index)
public E set(int index ,E element)
public boolean contains(Object o)
public boolean containsAll(Collection<?> C)
public E remove(int index ) 删除该列表中指定位置的元素
public boolean remove(Object o) 删除该列表中指定的元素
public boolean removeAll(Collection<?> c)
public boolean retainAll(collection<?> c)
size() 获取长度

//282用到的一些方法
//Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive.
List<E> subList(int fromIndex, int toIndex);
//Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception
default void forEach(Consumer<? super T> action) 
//将子列表中的string加入到stringbuilder中
cur.subList(1, cur.size()).forEach(v -> sb.append(v));

易错点:
ConcurrentModificationException
不可在foreach遍历的时候直接对ArrayList进行修改(add/remove)等。安全的解决办法有:1.使用Iterator。2.复制到新的ArrayList并遍历新的ArrayList,删除原List的元素
详见:https://blog.csdn.net/oldshaui/article/details/84307013

LinkedList

LC-56. Merge Intervals
选择了LinkedList,而不是ArrayList。因为要使用LinkledList的getLast方法获取最后一个元素和toArray方法转成二维数组。
后面一个方法ArrayList也有,而且ArrayList可以通过get(size() - 1)获取最后一个元素,所以也不是刚需。

//Params:a – the array into which the elements of the list are to be stored, if it is big enoug
public <T> T[] toArray(T[] a) 

//LinkedList转二维数组的写法
LinkedList<int[]> result = new LinkedList<>();
 return result.toArray(new int[result.size()][]);

Arrays类

  • asList()
    将多个元素转化为List。一些易错点:
  1. asList接收的是一个泛型变长参数(基本类型是不能泛型化的,就是说8种基本类型不能作为泛型参数,要想作为泛型参数就要使用其所对应的包装类),如果传入int[],会把它作为一个数组对象传入,如果传入Integer[],会把里面的每一个Integer传入,因而导致size不同。
  2. Arrays的asList方法使用的ArrayList类是一个内部定义的类,而不是java.util.ArrayList类,因此不能对其进行add,remove等操作。如需操作需要进一步转换:
List<String> pets = new ArrayList<String>(Arrays.asList("a", "b", "c")); 

3.Arrays.asList()产生的list对象会使用底层数组作为其物理实现,只要执行操作修改这个list就会修改原来的数组。要想不改变原来数组,就要在另一个容器中创建一个副本,写法如下

List<String> pets = new ArrayList<String>(Arrays.asList("a", "b", "c")); 

详情见Java Arrays.asList()方法详解

  • fill()
    填充数组。
Arrays.fill(dis, -1);  //全部数组元素赋值位-1
  • sort()
    排序。时间复杂度为O(nlogn),空间复杂度为O(logn)。注意,Arrays.sort()和Collections.sort()的区别在于,前者可以对多种的数组排序(比如char[],int[],long[]等),而集合类的排序要依靠后者(比如对List类排序)
Arrays.sort(a);  //默认升序
Arrays.sort(a, Collections.reverseOrder());  //降序
Arrays.sort(a, 0, 2);  //startIndex, endIndex

栈(深度优先DFS)

Queue<Integer> stack = new LinkedList<Integer>();
stack.push(i); //入栈
stack.pop();  //出栈

队列(广度优先BFS)

Queue<Integer> queue = new LinkedList<Integer>();
queue.offer(i); //入队
queue.poll();  //出队
queue.isEmpty();  //判断是否为空

一个可能只有我犯过的毛病= =:
一种比较经典的BFS写法,但是我下面这段代码有个bug:

            for (int i = 1; i <= tree.size(); i++) {
                TreeNode tn = tree.poll();
                if (tn.left != null) {
                    tree.offer(tn.left);
                } 
                if (tn.right != null) {
                    tree.offer(tn.right);
                }
            }

这段代码的问题在哪呢,在tree.size()是一个动态随poll和offer,应该要在循环前先确定这个值。

            int size = tn.size();
            for (int i = 1; i <= size; i++) {
                TreeNode tn = tree.poll();
                if (tn.left != null) {
                    tree.offer(tn.left);
                } 
                if (tn.right != null) {
                    tree.offer(tn.right);
                }
            }

优先队列(PriorityQueue)/ 堆

//小顶堆
PriorityQueue<String> small=new PriorityQueue<>();
//大顶堆
PriorityQueue<String> big=new PriorityQueue<>(Collections.reverseOrder());
//常用方法
peek()//返回队首元素
poll()//返回队首元素,队首元素出队列
add()/offer() //添加元素
size()//返回队列元素个数
isEmpty()//判断队列是否为空,为空返回true,不空返回false

//自定义排序,出自LC-451的题解[【宫水三叶】数据结构运用模拟题](https://leetcode-cn.com/problems/sort-characters-by-frequency/solution/gong-shui-san-xie-shu-ju-jie-gou-yun-yon-gst9/)
        PriorityQueue<Node> q = new PriorityQueue<>((a,b)->{
            if (b.v != a.v) return b.v - a.v;
            return a.c - b.c;
        });
    class Node {
        char c; 
        int v;
        Node(char _c, int _v) {
            c = _c; v = _v;
        }
    }

字符串、char相关

char数组和String互转

char[] charArray = s.toCharArray();
String s = new String(charArray);

String与int互转

//String 转 int,能够去掉前导0
int i = Integer.parseInt("000000199");
System.out.println(i);
//两种int转String的方法
String s = Integer.toString(i);
System.out.println(s);
String s1 = String.valueOf(i);
System.out.println(s1);

char与int互转

char c = (char) ( '0' + 1);
int i = c - '0';
int v = Character.getNumericValue('2');  //高端版,大小写字母在10-35之间,数字直接转换,如果不能转换返回-1

String类重要方法
基本类型,如果涉及较多字符串修改操作,建议使用StringBuilder
参考Java String Methods
charAt()
compareTo()
contains()
endsWith()
equals()
equalsIgnoreCase()
indexOf()
lastIndexOf()
length()

public String replace(char oldChar, char newChar)
public String replaceFirst(String regex, String replacement)
public String replaceAll(String regex, String replacement)
public String[] split(String regex) 
public String toLowerCase() 
 public String toUpperCase()
public String trim()
public char[] toCharArray()
//类方法
public static String valueOf(Object obj)
public static String format(String format, Object... args) {
        return new Formatter().format(format, args).toString();
    }

StringBuilder和StringBuffer
两个类都是在对String进行修改的时候使用。区别是StringBuilder 的方法不是线程安全的(不能同步访问),但是有速度优势。

//这里介绍主要使用的StringBuilder的方法
//constructor
public StringBuilder(int capacity)
public StringBuilder(String str) 

//末尾添加,overload了多个类型,基本上基本类型都覆盖了
public StringBuilder append(String str)
public StringBuilder append(char c)

 sb.append("abc");  //添加
 sb.append('c');  //可以直接添加char类型,不用转化为String!!!

//删除
public StringBuilder deleteCharAt(int index)
public StringBuilder delete(int start, int end)
 sb.delete(5,8);  //删除第[5,8)位

//替换
public StringBuilder replace(int start, int end, String str)

//插入,overload了多个类型,基本上基本类型都覆盖了
public StringBuilder insert(int offset, String str) 
public StringBuilder insert(int offset, char c)
 sb.insert(7, "bf");  //从第7位插入字符串

//index相关
public int indexOf(String str)
public int indexOf(String str, int fromIndex)
public int lastIndexOf(String str)
public int lastIndexOf(String str, int fromIndex)

//转String
 sb.reverse.toString();  //反转后转为String
public String substring(int start, int end)
public String substring(int start)

Character类
判断char是小写字母、大写字母、数字不需要写c - '0' >=0 && c - '0' <= 9这种了。

System.out.println(Character.isLowerCase('a'));
System.out.println(Character.isLowerCase('A'));
System.out.println(Character.isUpperCase('A'));
System.out.println(Character.isDigit('0'));
// true
// false
// true 
// true

Math

Math.max(a,b)
Math.min(a,b)
Math.abs(-1);

位运算

  • Integer.bitCount
    Returns the number of one-bits in the two's complement binary representation of the specified int value
    LC-1239
int i = 7; //111
int j = Integer.bitCount(i);  //3

技巧:
当一组对象(比如数组),每个对象(比如元素)只有两种状态的时候,可以考虑使用二进制表示。
例子:LC37-解数独

Stream

参考玩转Java8Stream(一、从零认识Stream)
出现在743. Network Delay Time,作用是找出dist这个数组的最大值并转化为int。

int[] dist = new int[n];
int max = Arrays.stream(dist).max().getAsInt();

System

数组深拷贝

//    public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
System.arraycopy(nums, 0, numsSorted, 0, nums.length);

Comparable/Comparator

写BFS题经常要用到自定义类,且由于要进行排序(PriorityQueue)需要进行比较。Comparable是一个接口,如果要实现这个接口,需要覆盖compareTo方法。有两种写法

//第一种写法(用了泛型),推荐
    class Cell implements Comparable<Cell> {
        int height;
        public Cell(int height) {
            this.height = height;
        }
        public int compareTo(Cell o) {
            return this.height - o.height;
        }
    }

//第二种写法
    class Cell implements Comparable {
        int height;
        public Cell(int height) {
            this.height = height;
        }
        public int compareTo(Object o) {
            Cell other = (Cell) o;
            return this.height - other.height;
        }
    }

Comparator是比较器,一般在排序方法(Collections.sort()..)/构造方法中传入,需要重写comapre方法。

//写法1,lambda,推荐
        PriorityQueue<Node> q = new PriorityQueue<>((a,b)->{
            if (b.v != a.v) return b.v - a.v;
            return a.c - b.c;
        });
//写法2,完整函数
        PriorityQueue<Cell> queue = new PriorityQueue<>(1, new Comparator<Cell>(){
            public int compare(Cell a, Cell b) {
                return a.height - b.height;
            }
        });

Equals和HashCode方法

自定义类的时候,如果需要在Set中加入自定义类,可能需要重写equals和hashcode方法。equals参考下面的写法(接收Obejct,需要先判断类型),hashcode需要认真考虑。

//以下来自1293. Shortest Path in a Grid with Obstacles Elimination的solution
class StepState {
    int steps, row, col, k;
    public StepState(int steps, int row, int col, int k) {
        this.steps = steps;
        this.row = row;
        this.col = col;
        this.k = k;
    }
    
    public int hashCode() {
        return this.row * this.col * this.k;
    }
    
    public boolean equals(Object other) {
        if (!(other instanceof StepState)) {
            return false;
        }
        StepState otherState = (StepState) other;
        return (this.row == otherState.row) && (this.col == otherState.col) && (this.k == otherState.k);
    }
    
    public String toString() {
        return String.format("%d %d %d", this.row, this.col, this.k);
    }
}

复杂度

HashMap:
Hashmap best and average case for Search, Insert and Delete is O(1) and worst case is O(n).

TreeMap:
插入、查找、删除:O(logN)

String和StringBuilder
String直接在后面添加String的复杂度是O(N),可以把String看成是一个char[],"abc" + "def"的复杂度是O(N),因为这种操作要创建新的string
StringBuilder append的复杂度是O(1),StringBuilder还是原本的对象。它的toString操作是O(N)

参考:java各集合的时间复杂度
数组 Array
访问:O(1)
查找(未排序):O(n)
查找(已排序):O(log n)
插入/删除:不支持

数组列表 ArrayList
添加: O(1)
删除:O(n)
查询:O(n)
求长:O(1)

链接列表 LinkedList
插入:O(n)
删除:O(n).
查找:O(n)

双链接列表 Doubly-Linked List
插入:O(n)
删除:O(n)
查找:O(n)

栈 Stack
压栈:O(1)
出栈:O(1)
栈顶:O(1)
查找:O(n)

队列 Queue/Deque/Circular Queue
插入:O(1)
移除:O(1)
求长:O(1)

二叉搜索树 Binary Search Tree
插入:O(log n)
删除:O(log n)
求长:O(log n)
以上均为平均时间,最坏情况均为O(n)。

红黑树 Red-Black Tree
插入:O(log n)
删除:O(log n)
求长:O(log n)
以上均为平均时间,最坏情况均为O(n)。

堆 Heap/PriorityQueue
插入:O(log n)
删除最大/小值:O(log n)
抽取最大/小值:O(log n)
查找最大/小值:O(1)
查找其他值:O(n)
删除:O(n)

映射 HashMap/Hashtable/HashSet
插入或删除:O(1)
调整容量:O(n)
查询:O(1)

易错点

  1. 遍历终点与长度改变
    写遍历的时候经常喜欢写这种,当不进行增删操作的时候,这种写法是可行的,但是一旦长度发生改变,可能会导致数组越界等问题。
        String s = "sdf";
        for (int i = 0; i < s.length(); ++i) {
            char c = s.charAt(i);
        }

最好是使用这种写法。

        int size = s.length();
        for (int i = 0; i < size; ++i) {
            char c = s.charAt(i);
        }

2.用getOrDefault的时候,务必主要后面要跟put。比如下面这段就经常忘记写put导致结果不对。

        int id = serialToId.getOrDefault(serial, num);
        if (id == num) num++;
        serialToId.put(serial, id);
  1. Java does not support arrays of generic classes
    所以
//正确的写法1
TreeMap<Integer, Integer>[] A;
A = new TreeMap[length];
//错误的写法1
TreeMap<Integer, Integer>[] A;
A = new TreeMap<>[length];
//错误的写法2
TreeMap<Integer, Integer>[] A;
A = new TreeMap<Integer, Integer>[length];

注意,在initialize 数组之后每个元素,也就是treeMap也要initialize,也就是要new

记忆tips

  1. 数组长度、字符串相关的(String, StringBuilder)的长度用的是length,集合类常用的是size表示元素数量
  2. 增删使用的动词不一样
    StringBuilder: append, delete
    ArrayList: add, remove
    Set: add, remove
    HashMap: put, remove
    Deque(queue): offer, poll
    Deque(stack): push, pop
    PriorityQueue:add/offer,poll

一些书写要点

  1. 要考虑好到底使用什么限定符,尽量给最低的权限即使用private。
  2. 涉及到sum的,考虑是否要使用Long,虽然单个数字可能在int范围内,但是加起来可能会溢出!
    3.返回空数组的写法是,return new int[0]; LC 210题。
    4.如果最后要判断一下再返回结果的,比如这种
        if (stack.size() > 0) {
            return false;
        }
        return true;

可以简化写成

return stack.size() == 0;

相关文章

网友评论

      本文标题:算法题常用Java方法与技巧

      本文链接:https://www.haomeiwen.com/subject/qxjeyltx.html