接触 Android 开发也有一段时间了,前段时间便开始想抽空整理一些知识点,通过笔记整理的方式减少自己重复学习的时间成本和提高自身的效率。

本文总结的部分是常见面试算法题,算法题解均有 java 实现。目录可以在右边侧边栏查看跳转。

之后会整理的知识点还会有 java、Android SDK、Android 源码、其他的一些计算机基础以及常见的面试题等几个部分,往后的一个月时间里会陆续补充更新,在 Github 上创建了项目,想关注的欢迎 star

另外,可查看上一篇:

字符串处理

生成括号

给定 n,表示有 n 对括号, 请写一个函数以将其生成所有的括号组合,并返回组合结果。

public List<String> generateParenthesis(int n) {
    List<String> res = new ArrayList<>();
    helper(n, n, "", res);
    return res;
}
// DFS
private void helper(int nL, int nR, String parenthesis, List<String> res) {
    // nL 和 nR 分别代表左右括号剩余的数量
    if (nL < 0 || nR < 0) {
        return;
    }
    if (nL == 0 && nR == 0) {
        res.add(parenthesis);
        return;
    }
    helper(nL - 1, nR, parenthesis + "(", res);
    if (nL >= nR) {
        return;
    }
    helper(nL, nR - 1, parenthesis + ")", res);
}
复制代码

Excel表列标题

给定一个正整数,返回相应的列标题,如Excel表中所示。如1 -> A,2 -> B...26 -> Z,27 -> AA

public String convertToTitle (int n) {
    StringBuilder str = new StringBuilder();
    while (n > 0) {
        n--;
        str.append ( (char) ( (n % 26) + 'A'));
        n /= 26;
    }
    return str.reverse().toString();
}
复制代码

翻转游戏

翻转游戏:给定一个只包含两种字符的字符串:+和-,你和你的小伙伴轮流翻转"++"变成"--"。当一个人无法采取行动时游戏结束,另一个人将是赢家。编写一个函数,计算字符串在一次有效移动后的所有可能状态。

public List<String> generatePossibleNextMoves (String s) {
    List list = new ArrayList();
    for (int i = -1; (i = s.indexOf ("++", i + 1)) >= 0;) {
        list.add (s.substring (0, i) + "--" + s.substring (i + 2));
    }
    return list;
}
复制代码

翻转字符串中的单词

给定一个字符串,逐个翻转字符串中的每个单词。

public String reverseWords(String s) {
    if(s.length() == 0 || s == null){
        return " ";
    }
    //按照空格将s切分
    String[] array = s.split(" ");
    StringBuilder sb = new StringBuilder();
    //从后往前遍历array,在sb中插入单词
    for(int i = array.length - 1; i >= 0; i--){
        if(!array[i].equals("")) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            sb.append(array[i]);
        }
    }
    return sb.toString();
}
复制代码

转换字符串到整数

实现atoi这个函数,将一个字符串转换为整数。如果没有合法的整数,返回0。如果整数超出了32位整数的范围,返回INT_MAX(2147483647)如果是正整数,或者INT_MIN(-2147483648)如果是负整数。

public int myAtoi(String str) {
    if(str == null) {
        return 0;
    }
    str = str.trim();
    if (str.length() == 0) {
        return 0;
    }
    int sign = 1;
    int index = 0;
    if (str.charAt(index) == '+') {
        index++;
    } else if (str.charAt(index) == '-') {
        sign = -1;
        index++;
    }
    long num = 0;
    for (; index < str.length(); index++) {
        if (str.charAt(index) < '0' || str.charAt(index) > '9') {
            break;
        }
        num = num * 10 + (str.charAt(index) - '0');
        if (num > Integer.MAX_VALUE ) {
            break;
        }
    }   
    if (num * sign >= Integer.MAX_VALUE) {
        return Integer.MAX_VALUE;
    }
    if (num * sign <= Integer.MIN_VALUE) {
        return Integer.MIN_VALUE;
    }
    return (int)num * sign;
}
复制代码

最长公共前缀

public String longestCommonPrefix(String[] strs) {
    if (strs == null || strs.length == 0) {
        return "";
    }
    String prefix = strs[0];
    for(int i = 1; i < strs.length; i++) {
        int j = 0;
        while (j < strs[i].length() && j < prefix.length() && strs[i].charAt(j) == prefix.charAt(j)) {
            j++;
        }
        if( j == 0) {
            return "";
        }
        prefix = prefix.substring(0, j);
    }
    return prefix;
}
复制代码

回文数

判断一个正整数是不是回文数。回文数的定义是,将这个数反转之后,得到的数仍然是同一个数。

public boolean palindromeNumber(int num) {
    // Write your code here
    if(num < 0){
        return false;
    }
    int div = 1;
    while(num / div >= 10){
        div *= 10;
    }
    while(num > 0){
        if(num / div != num % 10){
            return false;
        }
        num = (num % div) / 10;
        div /= 100;
    }
    return true;
}
复制代码

动态规划

单词拆分

给定字符串 s 和单词字典 dict,确定 s 是否可以分成一个或多个以空格分隔的子串,并且这些子串都在字典中存在。

public boolean wordBreak(String s, Set<String> dict) {
    // write your code here
    int maxLength = getMaxLength(dict);
    // 长度为n的单词 有n + 1个切割点 比如: _l_i_n_t_
    boolean[] canBreak = new boolean[s.length() + 1];
    // 当s长度为0时
    canBreak[0] = true;
    for(int i = 1; i < canBreak.length; i++){
        for(int j = 1; j <= maxLength && j <= i; j++){
            //i - j 表示从 i 点开始往前j个点的位置
            String str = s.substring(i - j,i);
            //如果此str在词典中 并且 str之前的 字符串可以拆分     
            if(dict.contains(str) && canBreak[i - j]){
                canBreak[i] = true;
                break;
            }
        }
    }
    return canBreak[canBreak.length - 1];
}
private int getMaxLength(Set<String> dict){
    int max = 0;
    for(String s : dict){
        max = Math.max(max,s.length());
    }
    return max;
}
复制代码

爬楼梯

假设你正在爬楼梯,需要n步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法爬到楼顶部?

public int climbStairs(int n) {
    if (n == 0) return 0;
    int[] array = new int[n + 1];
    array[0] = 1;
    if (array.length > 1) {
        array[1] = 1;
    }
    for(int i = 2; i < array.length; i++) {
        array[i] = array[i - 1] + array[i - 2];
    }
    return array[n];
}
复制代码

打劫房屋

假设你是一个专业的窃贼,准备沿着一条街打劫房屋。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,在不触动报警装置的情况下, 你最多可以得到多少钱 。

public long houseRobber(int[] A) {
    if (A.length == 0) return 0;
    long[] res = new long[A.length + 1];
    res[0] = 0;
    res[1] = A[0];
    for (int i = 2; i < res.length; i++) {
        res[i] = Math.max(res[i - 2] + A[i - 1], res[i - 1]);
    }
    return res[A.length];
}
复制代码

编辑距离

给出两个单词word1和word2,计算出将word1 转换为word2的最少操作次数。你总共三种操作方法:插入一个字符、删除一个字符、替换一个字符。

public int minDistance(String word1, String word2) {
    // write your code here
    int n = word1.length();
    int m = word2.length();
    int[][] dp = new int[n + 1][m + 1];
    for (int i = 0; i < n + 1; i++){
        dp[i][0] = i;
    }
    for (int j = 0; j < m + 1; j++){
        dp[0][j] = j;
    }
    for (int i = 1; i< n + 1; i++){
        for (int j = 1; j < m + 1; j++){
            if (word1.charAt(i - 1) == word2.charAt(j - 1)){
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j]));
            }
        }
    }
    return  dp[n][m];
}
复制代码

乘积最大子序列

public int maxProduct(List<Integer> nums) {
    // 分别记录正数最大值和负数最小值
    int[] max = new int[nums.size()];
    int[] min = new int[nums.size()];
    min[0] = max[0] = nums.get(0);
    int result = nums.get(0);
    for (int i = 1; i < nums.size(); i++) {
        min[i] = max[i] = nums.get(i);
        if (nums.get(i) > 0) {
            max[i] = Math.max(max[i], max[i - 1] * nums.get(i));
            min[i] = Math.min(min[i], min[i - 1] * nums.get(i));
        } else if (nums.get(i) < 0) {
            max[i] = Math.max(max[i], min[i - 1] * nums.get(i));
            min[i] = Math.min(min[i], max[i - 1] * nums.get(i));
        }
        result = Math.max(result, max[i]);
    }
    return result;
}
复制代码

矩阵

螺旋矩阵

给定一个包含 m x n 个要素的矩阵,(m 行, n 列),按照螺旋顺序,返回该矩阵中的所有要素。

public List<Integer> spiralOrder(int[][] matrix) {
    ArrayList<Integer> rst = new ArrayList<Integer>();
    if(matrix == null || matrix.length == 0) {
        return rst;
    }
    int rows = matrix.length;
    int cols = matrix[0].length;
    int count = 0;
    while(count * 2 < rows && count * 2 < cols){
        for (int i = count; i < cols - count; i++) {
            rst.add(matrix[count][i]);
        }
        for (int i = count + 1; i < rows - count; i++) {
            rst.add(matrix[i][cols - count - 1]);
        }
        if (rows - 2 * count == 1 || cols - 2 * count == 1) { // 如果只剩1行或1列
            break;
        }
        for (int i = cols - count - 2; i >= count; i--) {
            rst.add(matrix[rows - count - 1][i]);
        }
        for (int i = rows - count - 2; i >= count + 1; i--) {
            rst.add(matrix[i][count]);
        }
        count++;
    }
    return rst;
}
复制代码

判断数独是否合法

请判定一个数独是否有效。该数独可能只填充了部分数字,其中缺少的数字用 . 表示。

维护一个HashSet用来记同一行、同一列、同一九宫格是否存在相同数字

public boolean isValidSudoku(char[][] board) {
    Set seen = new HashSet();
    for (int i=0; i<9; ++i) {
        for (int j=0; j<9; ++j) {
            char number = board[i][j];
            if (number != '.')
                if (!seen.add(number + " in row " + i) ||
                    !seen.add(number + " in column " + j) ||
                    !seen.add(number + " in block " + i / 3 + "-" + j / 3))
                    return false;
        }
    }
    return true;
}
复制代码

旋转图像

给定一个N×N的二维矩阵表示图像,90度顺时针旋转图像。

public void rotate(int[][] matrix) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
        return;
    }
    int length = matrix.length;
    for (int i = 0; i < length / 2; i++) {
        for (int j = 0; j < (length + 1) / 2; j++){
            int tmp = matrix[i][j];
            matrix[i][j] = matrix[length - j - 1][i];
            matrix[length -j - 1][i] = matrix[length - i - 1][length - j - 1];
            matrix[length - i - 1][length - j - 1] = matrix[j][length - i - 1];
            matrix[j][length - i - 1] = tmp;
        }
    }   
}
复制代码

二进制 / 位运算

落单的数

给出 2 * n + 1个数字,除其中一个数字之外其他每个数字均出现两次,找到这个数字。

异或运算具有很好的性质,相同数字异或运算后为0,并且具有交换律和结合律,故将所有数字异或运算后即可得到只出现一次的数字。

public int singleNumber(int[] A) {
    if(A == null || A.length == 0) {
        return -1;
    }
    int rst = 0;
    for (int i = 0; i < A.length; i++) {
        rst ^= A[i];
    }
    return rst;
}
复制代码

格雷编码

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个二进制的差异。给定一个非负整数 n ,表示该代码中所有二进制的总数,请找出其格雷编码顺序。一个格雷编码顺序必须以 0 开始,并覆盖所有的 2n 个整数。例子——输入:2;输出:[0, 1, 3, 2];解释: 0 - 00,1 - 01,3 - 11,2 - 10

格雷码生成公式:G(i) = i ^ (i >> 2)

public ArrayList<Integer> grayCode(int n) {
    ArrayList<Integer> result = new ArrayList<Integer>();
    for (int i = 0; i < (1 << n); i++) {
        result.add(i ^ (i >> 1));
    }
    return result;
}
复制代码

其他

反转整数

将一个整数中的数字进行颠倒,当颠倒后的整数溢出时,返回 0 (标记为 32 位整数)。

public int reverseInteger(int n) {
    int reversed_n = 0;
    while (n != 0) {
        int temp = reversed_n * 10 + n % 10;
        n = n / 10;
        if (temp / 10 != reversed_n) {
            reversed_n = 0;
            break;
        }
        reversed_n = temp;
    }
    return reversed_n;
}
复制代码

LRU缓存策略

为最近最少使用(LRU)缓存策略设计一个数据结构,它应该支持以下操作:获取数据(get)和写入数据(set)。获取数据get(key):如果缓存中存在key,则获取其数据值(通常是正数),否则返回-1。 写入数据set(key, value):如果key还没有在缓存中,则写入其数据值。当缓存达到上限,它应该在写入新数据之前删除最近最少使用的数据用来腾出空闲位置。

public class LRUCache {
    private class Node{
        Node prev;
        Node next;
        int key;
        int value;
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
            this.prev = null;
            this.next = null;
        }
    }
    private int capacity;
    private HashMap<Integer, Node> hs = new HashMap<Integer, Node>();
    private Node head = new Node(-1, -1);
    private Node tail = new Node(-1, -1);
    public LRUCache(int capacity) {
        this.capacity = capacity;
        tail.prev = head;
        head.next = tail;
    }
    public int get(int key) {
        if( !hs.containsKey(key)) {    		//key找不到
            return -1;
        }
        // remove current
        Node current = hs.get(key);
        current.prev.next = current.next;
        current.next.prev = current.prev;
        // move current to tail
        move_to_tail(current);			//每次get,使用次数+1,最近使用,放于尾部
        return hs.get(key).value;
    }
    public void set(int key, int value) {			//数据放入缓存
        // get 这个方法会把key挪到最末端,因此,不需要再调用 move_to_tail
        if (get(key) != -1) {
            hs.get(key).value = value;
            return;
        }
        if (hs.size() == capacity) {		//超出缓存上限
            hs.remove(head.next.key);		//删除头部数据
            head.next = head.next.next;
            head.next.prev = head;
        }
        Node insert = new Node(key, value);		//新建节点
        hs.put(key, insert);
        move_to_tail(insert);					//放于尾部
    }
    private void move_to_tail(Node current) {    //移动数据至尾部
        current.prev = tail.prev;
        tail.prev = current;
        current.prev.next = current;
        current.next = tail;
    }
}
复制代码

本次的分享就到这啦,喜欢的话可以点个赞👍或关注。如有错误的地方欢迎大家在评论里指出。

本文为个人原创,转载请注明出处。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐