算法分类

双指针法

使用条件

  • 滑动窗口 (90%)
  • 时间复杂度要求O(n) (80%)
  • 要求原地操作,只可以使用交换,不能使用额外空间(80%)
  • 有子数组subarray/子字符串substring的关键词(50%)
  • 有回文palindrome关键词(50%)

相向双指针,同向双指针,合并双指针。。。

167、两双相加 i=0, j=nums.length - 1

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int i = 0, j = numbers.length -1;
        while(i < j) {
            int sum = numbers[i] + numbers[j];
            if(sum == target) {
                return new int[]{i+1, j+1};
            } else if (sum < target) {
                i++;
            } else {
                j--;
            }
        }
        return null;
    }
}

633、两数平方和 i=0, j= (int)Math.sqrt©

345 、反转字符串中的元音字符 i=0, j = s.length() -1

680、回文字符串 i=0, j = s.length() - 1

88、归并两个有序数组 index1 = m-1, index2 = n-1

141、判断链表是否存在环 l1 = head, l2 = head.next

524、最长子序列 ***

思路:两个指针遍历两字符串中字符,判断是否相等

class Solution {
    public String findLongestWord(String s, List<String> dictionary) {
        String longestWord = "";
        for(String str : dictionary) {
            int l1 = longestWord.length(), l2 = str.length();
            if(l1 > l2 || (l1 == l2 && longestWord.compareTo(str) < 0)) {
                continue;
            }
            if(isValid(s, str)) {
                longestWord = str;
            }
        }
        return longestWord;
    }
    private boolean isValid(String s, String str) {
        int i = 0, j = 0;
        while(i< s.length() && j<str.length()) {
            if(s.charAt(i) == str.charAt(j)) {
                j++;
            }
            i++;
        }
        return j == str.length();
    }
}

排序

快速排序

堆排序 PriorityQueue

215、找Top K大的数

class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for(int val : nums) {
            pq.add(val);
            if(pq.size() > k) {
                pq.poll();
            }
        }
        return pq.peek();
    }
}

桶排序

347、出现频率最多的K个数 ***

思路:使用Map存储数及其出现的频率,然后转换为桶,桶用ArrayList实现,桶的下标代表桶中数出现的的频率,即第i个桶中存储的数出现的频率为i。
把数都放到桶之后,从后向前遍历桶,最先得到的k个数就是出现频率最多的k个数。

在这里插入代码片

451、按照字符出现次数对字符串排序 Map<Character, Integer>
75、按颜色进行排序 荷兰国旗问题 ***

贪心思想

(保证每次操作都局部最优,并且最后得到的结果是全局最优的)
455、分配饼干 先排序,再做
435、不重叠的区间个数 按照区间的结尾进行排序,每次选择结尾最小,且和前一个区间不重叠的区间
452、投飞镖刺破气球 和435类似
406、根据身高和序号重组队列 ***
763、分割字符串使同种字符出现在一起 **
602、种植花朵
392、判断是否为子序列
665、修改一个数成为非递减数组 修改策略,统计修改次数
122、股票的最大收益
53、子数组最大的和
121、买入和售出股票最大的收益

二分查找

  1. 排序数组(30%-40%)
  2. 时间复杂度比O(n)更小(99%)
  3. 找到数组中的一个分割位置,使得左半部分满足某条件,右半部分不满足(100%)
  4. 找到一个最大/最小的值使得某个条件被满足(90%)

正常实现: l=0, h = nums.length - 1, while(l <= h)

public int binarySearch(int[] nums, int key) {
	int l = 0, h = nums.length - 1;
	// 循环条件
	while( l <= h ) {
		int m = l + (h - l) / 2;
		if(nums[m] == key) {
			return m;
		} else if(nums[m] > key) {
			// h的赋值表达式
			h = m -1;
		} else {
			l = m + 1;
		}
	}
	// 最后的返回
	return -1;
}

变种: while(l < h) h=m l = m+1 (注意:当循环中出现了h=m,则循环条件为l<k)

一个有重复元素中查找key的最左位置的代码:

public int binarySearch(int[] nums, int key) {
	int l = 0, h = nums.length - 1;
	// 循环条件
	while( l < h ) {
		int m = l + (h - l) / 2;
		if(nums[m] >= key) {
			// h的赋值表达式
			h = m;
		} else {
			l = m + 1;
		}
	}
	// 最后的返回
	return l;
}

变种的话注意循环条件,赋值表达式和输出。
不同问题不同分析,不能背题。

69、求开方

思路:将mid与x/mid比较

class Solution {
    public int mySqrt(int x) {
        if(x<=1) return x;
        int start =  1, end = x;
        while(start <= end) {
            int mid = start + (end - start)/2;
            int sqrt = x / mid;
            if(mid == sqrt) {
                return mid;
            } else if(mid > sqrt) {
                end = mid -1;
            } else {
                start = mid + 1;
            }
        }
        return end;
    }
}

744、大于给定元素的最小元素

思路:二分正常实现,找大于给定元素的最小值,由于while循环结束后会有l > h, 故返回letters[l]的值。

class Solution {
    public char nextGreatestLetter(char[] letters, char target) {
        int n = letters.length;
        int l = 0, h = n - 1;
        while(l <= h) {
            int m = l + (h - l)/2;
            if(letters[m] <= target) {
                l = m + 1;
            } else {
                h = m - 1;
            }
        }
        return l < n ? letters[l] : letters[0];
    }
}

540、有序数组中找出只有一个未重复两次的数 ***

思路: 看nums[m]和nums[m+1]是否相等
令index为数组中的位置,如果m为偶数,且m+1 < index, 则必有nums[m] == nums[m+1]; 当m+1 >= index, 则必有nums [m] != nums[m+1]。

class Solution {
    public int singleNonDuplicate(int[] nums) {
        if(nums.length == 1) return nums[0];
        int l = 0, h = nums.length -1;
        while( l < h) {
            int mid = l + (h - l) / 2;
            //确保偶数  序号为: 0,1,2,3,4,5...
            // 0, 2, 4 才是偶数
            if(mid % 2 == 1) {
                mid -= 1;
            }
            //相等,则说明m+1 < index
            if( nums[mid] == nums[mid+1])
            {
                l = mid + 2;
            } else {
                h = mid;
            }
        }
        return nums[l];
    }
}

278、第一个错误的版本
153、寻找旋转数组的最小数字
34、查找区间

分治

241、给表达式加括号

宽度优先搜索BFS:(队列,标记)

使用条件:

  • 拓扑排序(100%)
  • 出现连通块的关键字 (100%)
  • 分层遍历 (100%)
  • 简单图最短路径(100%)
  • 给定一个变换规则,从初始状态变到终止状态最少几步(100%)
  • 计算在网格中从原点到特定点的最短路径长度,注意:BFS只能求解无权图的最短路径

时间复杂度O(n + m) 空间复杂度:O(n)
BFS必须用队列:Queue queue = new ArrayDueue<>();

695. 岛屿的最大面积

思路:遍历二维数组,对于每一块土地,也是去前后左右找相邻土地,只不过把找到的土地放到队列中
新土地加入队列的同时,岛屿面积area + 1,visited[nx][ny]置true;

class Solution {
    private final int[][] directons = new int[][]{{0,1},{0,-1},{1,0},{-1,0}};
    private int rows;
    private int cols;
    private int[][] grid;
    private boolean[][] visited;
    
    public int maxAreaOfIsland(int[][] grid) {
        int maxArea = 0;
        rows = grid.length;
        if(rows == 0) {
            return 0;
        }
        this.grid = grid;
        cols = grid[0].length;
        visited = new boolean[rows][cols];
        for(int i=0; i<rows; i++) {
            for(int j=0; j<cols; j++) {
                if(!visited[i][j] && grid[i][j] == 1) {
                    maxArea = Math.max(bfs(i, j), maxArea);
                }
            }
        }
        return maxArea;
    }
    private int bfs(int i, int j) {
        Queue<Integer> queue = new LinkedList<>();
        int area = 1;
        queue.add(i * cols + j);
        visited[i][j] = true;
        while(!queue.isEmpty()) {
            int cur = queue.poll();
            int curX = cur / cols;
            int curY = cur % cols;
            for(int k=0; k<4; k++) {
                int newX = curX + directons[k][0];
                int newY = curY + directons[k][1];
                boolean isArea = (newX >= 0 && newX < rows && newY >= 0 && newY < cols);
                if(isArea && grid[newX][newY] == 1 && !visited[newX][newY]) {
                    queue.add(newX * cols + newY);
                    visited[newX][newY] = true;
                    area ++;
                }
            }
        }
        return area;
    }
}




//简洁版BFS
class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int res = 0;
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                if(grid[i][j] == 1){
                    res = Math.max(res, bfs(grid, i, j));
                }   
            }
        }
        return res;
    }
    public int bfs(int[][] grid, int i, int j){
        int[] dx = {1, -1, 0, 0};
        int[] dy = {0, 0, 1, -1};
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{i, j});
        grid[i][j] = 0;
        int area = 1;
        while(!queue.isEmpty()){
            int[] x = queue.poll();
            for(int index = 0; index < 4; index++){
                int nx = x[0] + dx[index], ny = x[1] + dy[index];
                if(nx>=0 && nx < grid.length && ny >= 0 && ny < grid[0].length && grid[nx][ny] == 1){
                    grid[nx][ny] = 0;
                    area += 1;
                    queue.offer(new int[]{nx, ny});
                }
            }
        }
        return area;
    }
}

200、矩阵中的联通分量数目

注意:这种题用BFS,使用DFS有的环境会栈溢出
思路:使用队列存储,将二维下标转为int,注意是如何互转的。
并且需要一个visited数组,记录是否访问过,不然会有大量重复操作,会严重超时。

class Solution {
    private final static int[][] directions = {{-1,0},{0,-1},{1,0},{0,1}};
    private int rows;
    private int cols;
    private char[][] grid;
    private boolean[][] visited;

    public int numIslands(char[][] grid) {
        rows = grid.length;
        if(rows == 0) {
            return 0;
        }
        cols = grid[0].length;
        this.grid = grid;
        visited = new boolean[rows][cols];

        int count = 0;
        for(int i=0; i<rows; i++) {
            for(int j=0; j<cols; j++) {
                if(!visited[i][j] && grid[i][j] == '1') {
                    bfs(i, j);
                    count++;
                }
            }
        }
        return count;
    }

    private void bfs(int i, int j) {
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(i*cols + j);
        visited[i][j] = true;
        while(!queue.isEmpty()) {
            int cur = queue.poll();
            int curX = cur / cols;
            int curY = cur % cols;
            for(int k = 0; k<4; k++) {
                int newX = curX + directions[k][0];
                int newY = curY + directions[k][1];
                
                boolean inArea = (newX >= 0 && newX < rows && newY >=0 && newY < cols);
                if(inArea && grid[newX][newY] == '1' && !visited[newX][newY]) {
                    queue.offer(newX*cols + newY);
                    //入队列,立马标记
                    visited[newX][newY] = true;
                }
            }
        }
    }
}

279.组成整数的最小平方数数量

思路:先生成小于n的所有平方数集合,然后求解从节点n到节点0的最短路径。

class Solution {
    public int numSquares(int n) {
        List<Integer> squares = generateSquares(n);
        Queue<Integer> queue = new LinkedList<>();
        boolean[] marked = new boolean[n+1];
        queue.offer(n);
        int level = 0;
        while( !queue.isEmpty()) {
            int size = queue.size();
            level ++;
            while(size-- > 0) {
                int cur = queue.poll();
                for(int s : squares) {
                    int next = cur - s;
                    if (next < 0) {
                        break;
                    } 
                    if(next == 0) {
                        return level;
                    }
                    if(marked[next]) {
                        continue;
                    }
                    marked[next] = true;
                    queue.add(next);
                }
            }
        }
        return n;
    }
    private List<Integer> generateSquares(int n) {
        List<Integer> res = new ArrayList<>();
        int squ = 1;
        int diff = 3;
        while(squ <= n) {
            res.add(squ);
            squ += diff;
            diff += 2;
        }
        return res;
    }
}

127、最短单词路径

拓扑排序

方法一:使用入度表(BFS)广度优先遍历和贪心算法
方法二:DFS + 邻接矩阵

207、课程安排的合法性

思路:广度优先遍历和贪心算法
首先构造入度表和邻接矩阵,然后将所有入度为0的课程放入队列中,最后不停的使用BFS遍历队列,在出队列时,课程数减1,最后判断课程数是否为0了。

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //入度表
        int[] indegrees = new int[numCourses];
        //邻接矩阵
        List<List<Integer>> adj = new ArrayList<>();
        Queue<Integer> queue = new LinkedList<>();
        for(int i=0; i<numCourses; i++) {
            adj.add(new ArrayList<>());
        }
        for(int[] cp : prerequisites) {
            indegrees[cp[0]] ++;
            adj.get(cp[1]).add(cp[0]);
        }
        //获取所有入度为0的课程
        for(int i=0; i<numCourses; i++) {
            if(indegrees[i] == 0) {
                queue.add(i);
            }
        }
        //BFS
        while(!queue.isEmpty()) {
            int pre = queue.poll();
            numCourses --;
            for(int cur : adj.get(pre)) {
                if(--indegrees[cur] == 0) {
                    queue.add(cur);
                }
            }
        }
        return numCourses == 0;
    }
}

210、课程安排的顺序

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        List<Integer> res = new ArrayList<>();
        int[] indegrees = new int[numCourses];
        List<List<Integer>> adj = new ArrayList<>();
        for(int i=0; i<numCourses; i++) {
            adj.add(new ArrayList<>());
        }
        for(int[] cp : prerequisites) {
            indegrees[cp[0]] ++;
            adj.get(cp[1]).add(cp[0]);
        }
        Queue<Integer> queue = new LinkedList<>();
        for(int i=0; i<numCourses; i++) {
            if(indegrees[i] == 0)  queue.add(i);
        }
        while(!queue.isEmpty()) {
            int pre = queue.poll();
            numCourses --;
            res.add(pre);
            for(int cur : adj.get(pre)) {
                if(--indegrees[cur] == 0) queue.add(cur);
            }
        }
        if(numCourses == 0) {
            return res.stream().mapToInt(Integer::valueOf).toArray();
        } else {
            return new int[]{};
        }
    }
}

DFS:(栈,标记)

使用条件:

  • 找满足某个条件的所有方案(99%)
  • 二叉树的问题(90%)
  • 组合问题(95%)(问题模型:求所有满足条件的组合; 判断条件:组合中的元素顺序无关的)
  • 排列问题(95%) (问题模型:求所有满足条件的排列; 判断条件:组合中元素的顺序相关的)

不要使用DFS的场景:

  • 联通块问题(一定要用BFS,否则StackOverFlow)
  • 拓扑排序 (一定要用BFS, 否则StackOverFlow)
  • 一切BFS可以解决的问题。

复杂度:

  • 时间复杂度:O(方案个数 * 构造每个方案的时间)
  • 树的遍历:O(n)
  • 排列问题:O( n! * n )
  • 组合问题;O( 2^n * n )
public ReturnType dfs (参数) {
	if(递归出口) {
		记录答案;
		return;
	}
	for(所有的拆解可能性) {
		修改参数;
		dfs(参数);
		还原被修改的参数;
	}
	//如果需要的话,很多时候不要return
	return something;
}

DFS常用于求解可达性问题
695、查找最大的连通面积

200、矩阵中的联通分量数目

本题可用DFS,但是建议用BFS,消耗的空间更小

class Solution {
    int res = 0;
    int m;
    int n;
    int[][] dic = {{0,1},{1,0},{0,-1},{-1,0}};
    public int numIslands(char[][] grid) {
        m = grid.length;
        n=grid[0].length;
        for(int i=0; i<m; i++) {
            for(int j=0; j<n; j++) {
                if(grid[i][j] != '0') {
                    res++;
                    dfs(grid, i, j);
                }
            }
        }
        return res;
    }

    private void dfs(char[][] grid, int i, int j){
       if(i<0 || i>= m || j < 0 || j>= n || grid[i][j] == '0') return;
       grid[i][j] = '0';
       for(int[] d : dic) {
           dfs(grid, i + d[0], j + d[1]);
       }
    }

}

547、好友关系的联通分量数目
139、填充封闭区域
417、能到达的太平洋和大西洋的区域

回溯(属于DFS):

普通DSF主要用于可达性问题,而回溯主要用于求解排列组合问题。
回溯不是立即返回,故需要进行标记
17、数字键盘组合
93、IP地址划分 ***
79、在矩阵中寻找字符串
257、输出二叉树中所以从根到叶子的路径
46、排列
47、含有相同元素求排列
77、组合
39、组合求和
40、含有相同元素的求组合求和
216、数字的组合求和
78、子集合
90、含有相同元素求子集
131、分割字符串使用每个部分都是回文数
37、数独
51、N皇后

动态规划

使用场景:

  • 求方案总数(90%)
  • 求最值(90%)
  • 求可行性(80%)

不适合的场景:

  • 找所有具体的方案(99%)
  • 输入数据无序(除背包问题外 60%)
  • 暴力算法就已经是多项式时间复杂度

动态规划的四要素:

  • 状态:递归的定义
  • 转移方程:递归的拆解
  • 初始化:递归的出口
  • 执行顺序:递归的调用

常见的动态规划:

  • 背包型
  • 区间型:大区间依赖小区间
  • 匹配型
  • 划分型
  • 接龙型

70、爬楼梯 dp[i] = dp[i-1] + dp[i-2]
198、盗窃抢劫 dp[i] = max(dp[i-2] + nums[i], dp[i-1])
213、盗窃在环形街区抢劫
信件错排 dp[i] = (i-1)*dp[i-2] + (i-1)*dp[i-1]
母牛生产 dp[i] = dp[i-1] + dp[i-3]
64、矩阵的最小矩阵和
62、矩阵的总路径数

数组区间

301、数组区间和 前缀和
413、数组中等差递增子区间的个数 if(A[i] - A[i-1] == A[i-1] - A[i-2]) 则dp[i] = dp[i-1] + 1

分割整数

343、分割整数的最大乘积 ***
279、按平方数来分割整数
91、分割整数构成字母字符串

最长递增子序列

300、最长递增子序列 dp[n] = max{ 1, dp[i] + 1 | Si < Sn && i < n}
646 、一组整数对能够构成的最长链
376、最长摆动子序列

最长公共子序列(双序列型)

背包问题:

0-1背包 容量为N的背包 对物品的迭代在最外层
完全背包:物品数据为无限个 对物品的迭代在最里层 ***
多重背包:物品数量有限制
多维费用背包:物品不仅有重量,还有体积
其他:物品之间相互约束或依赖
416、划分数组为相等的两部分 可看成一个背包大小为sum/2的0-1背包问题
494、改变一组数的正负号使得它们的和为一给定数 可以0-1背包或DFS
139、字符串按单词列表分割 完全背包
474、01字符构成最多的字符串 多维费用的0-1背包问题
322、找零钱的最少硬币数 完全背包
377、组合总和 完全背包

股票交易

309、需要冷静期的股票交易
714、需要交易费用的股票交易
123、只能进行两次的股票交易
188、只能进行K次的股票交易

字符串编辑

583、删除两个字符串的字符使它们相等 -> 可转化为最长公共子序列问题
72、编辑距离
650、复制粘贴字符

数字

最大公约数gcd return b==0 ?a : gcd(b, a % b);
最大公倍数lcm return a * b / gcd(a, b);

素数

204、生成素数序列

进制转换

504、7进制
405、 16进制
168、 26进制

阶乘

172、统计阶乘尾部有多少个0 *** 百度面试题 尾部的0由2*5得到

字符串加法减法

67、二进制加法
415、字符串加法

相遇问题

462、改变数组元素使所有的数组元素都相等

多数投票问题

169、数组中出现次数多于n/2的元素
其他
367、平方数 平方序列;1,4 ,9,16…. 间隔:3,5,7…
326、3的n次方
238、乘积数组 left数组和right数组,相乘
628、找出数组中的乘积最大的三个数

链表

160、找出两个链表的交点
206、链表反转
21、归并两个有序的链表
83、从有序链表中删除重复节点 递归
19、删除链表的倒数第n节点 fast, slow
24、交换链表中相邻结点 ***
445、链表求和 栈
234、回文链表
725、分割链表
328、链表元素按奇偶聚集

104、树的高度
110、平衡树
543、两节点的最长路径
226、翻转树
617、归并两颗树
112、判断路径和是否等于一个数
437、统计路径和等于一个数的路径数量
572、子树
101、树的对称
111、最小路径
404、统计左叶子节点的和
687、相同节点值的最大路径长度
377、间隔遍历
671、找出二叉树中第二小的节点
层次遍历 (BFS)
637、一棵树每层节点的平均值
513、得到左下角的节点
前中后序遍历
144、非递归实现二叉树的前序遍历
145、非递归实现二叉树的后序遍历
94、非递归实现二叉树的中序遍历
二叉查找树BST
669、修剪二叉查找树
230、寻找二叉查找树的第k个元素
235、二叉查找树的最近公共祖先
236、二叉树的最近公共祖先
108、从有序数组中构建二叉查找树
109、根据有序链表构造平衡的二叉查找树
653、在二叉查找树中寻找两个节点,使它们的和为一个给定值
530、在二叉查找树中查找两个节点之差的最小绝对值
501、寻找二叉查找树中出现次数最多的值
Trie树
208、实现Trie树
677、Trie树,求前缀和

栈和队列

232、用栈实现队列
225、用队列实现栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值