【LeetCode】
贪心算法
605 种花问题E
- 补0,避免繁琐的边界条件判断
在左右边界补0,对0区进行判断一视同仁 - 跳步
判断条件cur == 0 && (next == 0 || next == 边界)
循环体中也i++,跳过下一个0
452 最少数量剑引爆气球M
- 贪的体现
按右边界从小到大排列后,因为这样能留出更多的气球,将引爆点设为右边界,只要cur < prev就可以引爆。尽可能多的引爆气球
435 无重叠区间M
类似于452
- 贪的体现
同样是按右边界从小到大排列,因为这样能够给右边留出更多的区间,右边界从小变大的情况下,尽可能去除掉与该区间重叠的区间。这样局部尽可能去除重叠区间,整体也能使去除的重叠区间最少。
使用comparator要注意有可能发生溢出的情况不要相减
双指针
滑动窗口
左边界和右边界交替移动的过程中,少考虑了很多不必要的空间
76 最小覆盖字符串H
-
暴力求解
s子串中出现的字符的频数,大于等于t中出现的字符的频数
复杂度分析:
时间:O(S^3+T)
空间: O(S+T)来自于频数数组 -
滑动窗口(HashMap)
通过滑动窗口与双指针,一次遍历
- 构造两个hashmap
- for令ori记录t中出现的字符频数
- 右指针从最左向右移动,移动过程中判断cnt中字符频数是否满足ori
- 若满足,则比较并记录最小覆盖字符串,同时左指针右移直到不满足情况出现
- 返回值
- 滑动窗口(distance)
利用两个char数组存储s, t字符
利用两个int数组存储s, t字符频数
利用distance作为滑动窗口比较条件(distance == tlen),降低比较次数
map.getOrDefault(c,0);,c有值取c,否则取默认0;Interator it = ori.entrySet().iterator;集合的迭代器,可以通过它遍历集合
while(iter.hasNext()){
Map.Entry entry = (Map.Entry) iter.next();
Character key = (Character)entry.getKey();
Integer val = (Integer)entry.getValue();
if(cnt.getOrDefault(key, 0) < val){
return false;
}
}
二分查找
69 求开方E
- 注意点
while内部<和≤的区别(因为循环体内可能永远无法return,所以l <= r可以解决出循环的问题)
注意边界溢出(long(m * m))
34 排序区间查找元素的第一个位置和最后一个位置M
- 注意点
循环体中有l=m这样的赋值,要注意取中点时需不需要向上取整
81 搜索旋转排序数组M
- 注意点
本题解法是通过m与l或r的判断来确定左右区间增减性,进而用target比较判断target位于增序区间还是无序区间
l m r三者相同时无法判断左右区间增减情况。所以需要进行预处理。
154 寻找旋转排序数组中最小值H
本题难点在于数组含有重复元素,如何判断
- 注意点
令m和右端点比较
如果m右区间单调增,则不考虑右区间的情况(这里r必须闭区间)
如果m左区间单调增,则不考虑左区间的情况(这里l可以开区间)
如果m和右端点相等,则右端点减一,继续查找
while(l < r){
m = (l+r)/2;
if(nums[m] < nums[r]){
r = m;//开闭区间的情况,这里r取闭区间,原因在于,r = m-1会越界
}else if(nums[m] > nums[r]){
l = m+1;
}else{
r = r-1;
}
}
540 有序数组中的单一元素M
具有单双数特点的排序数组,可以利用下标进行判断
排序算法
215 数组中的第k个最大元素M
- 暴力解法
- 优先队列
PriorityQueue<Integer> p = new PriorityQueue<>();
p.poll();
p.add(num);
p.peek();
-
快速排序
用来解决第k个最值问题
需要修改快排算法,并且增加对返回值的判断 -
堆排序
建立大顶堆,删除k-1个最大元素后,堆顶为答案
347 前k个高频元素M
- 堆排序
PriorityQueue<int[]> heap = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] o1, int[] o2){
return o1[1]-o2[1];
}
});
- 优先队列内排序
PriorityQueue<Map.Entry<Integer, Integer>> heap = new PriorityQueue<>((e1, e2) -> (e2.getValue() -e1.getValue());
搜索
深度优先搜索
695 岛屿最大面积M
- 思路:探索每一块区域,并探索与其中土地上下左右相连的每一块土地。
为了确保每块土地不会被重复访问,将探索过的土地置0
- dfs + 递归
从头开始,逐个遍历,遇到0则直接返回0;
若遇到1,ans+1,同时将该块区域置为0,再对上下左右四块区域使用dfs,直到四周都是0或到达边界。
选择上下左右的方法
int[] x = new int[]{0, 0, 1, -1};
int[] y = new int[]{1, -1, 0, 0};
for(int index = 0; index != 4; ++index){
int next_i = cur_i + x[index];
int next_j = cur_j + y[index];
ans += dfs(grid, next_i, next_j);
}
- dfs + 栈
用栈代替递归。每一次搜索,如果是土地,则将上下左右四块区域入栈,每次搜索都出栈进行判断,直到栈为空。
for{
for{
stacki.push(i);
stackj.push(j);
while(!empty){
int cur_i = stacki.pop(); //对出栈元素进行判断
int cur_j = stackj.pop();
if(grid != 1) continue; //grid不存在或为水地,则继续判断栈内元素
grid = 0;
cur++;
stacki.push(pre,next,top,down); //对周围区域进行入栈处理
stackj.push(pre,next,top,down);
}
}
}
417 太平洋大西洋水流问题M
DFS
- 题意:一块陆地,高低不平,左上大西洋,右下太平洋,求陆地上既能流入太平洋又能流入大西洋的区域
- 解法:逆向思维,水往高处流,大西洋的水能流入的区域和太平洋的水流入的区域,它们的交集就是答案。
- 步骤:
- 构建两个二维数组pa、oc分别存储符合题意的坐标
- 因为四条边临海,水必定能流经它们,所以从四条边开始搜索
- 两个数组各自进入dfs的区域设为1,最后计算都为1的区域,用ArrayList<List
> 存储 - dfs判断一块区域四个方向的区域海拔是否高于当前区域(且在当前大洋数组中未被置过0),高则继续dfs
//List是抽象类,new时不能放在右边
List<List<Integer> ans = new ArrayList<List<Integer>>(); //yes
回溯法
需要记录节点状态的优选搜索
修改最后一位输出,比如排列组合;
修改访问标记,比如矩阵里搜索字符串;
46 全排列M
- 题意:给定一个不含重复数字的数组 nums ,返回其所有可能的全排列。
- 解法:同样可以把这个数组看成和m*n式的问题一个解决思路。设置一个used数组用来标记。1---标记1---2---标记2--3---标记3---取出123---取消标记3---取消标记2---3---标记3---标记2---取出132...
- 步骤:
for{
for{
dfs
}
}
//dfs
//到达最深,则取出tmp
if(deep == len){
res.add(new ArrayList<>(tmp));
}
for{
if(used == false){
used = true;
tmp.add;
dfs(deep+1);
//回退操作
used = false;
tmp.remove(tmp.size()-1);
}
}
77 组合M
- 题意:整数n, k,从[1,n]范围中返回k个不同的组合
- 解法1:dfs
- 限制dfs中的next选择,避免边界判断,同时可以进行剪枝优化
for(int i = start; i <= n-k+tmp.size()+1; ++i) - 每一次添加
addLast后,要进行状态回收removeLast。 - tmp可以利用Deque堆栈进行判断。
- 输出格式避免引用地址的影响,采用
res.add(new ArrayList<>(tmp))
- 限制dfs中的next选择,避免边界判断,同时可以进行剪枝优化
- 解法2:二叉树
- 令每次搜索的判断为
选/不选 - 边界条件为
begin > n-k+1这里的k相当于解法1的(k-tmp.size()) - 不选,则继续dfs,且begin+1
- 选,则
addLast,继续dfs,且begin+1,k-1 - 选完后要进行状态回收,
removeLast,因为一直在维护一个tmp工作
- 令每次搜索的判断为
79 单词搜索M
- 题意: mxn的字符格中找出有无target单词。
- 解法:用boolean类型的dfs返回每一次深度搜索后的结果,其中需要对已经取过的字符做标记,同时若当前字符四周没有符合target的下一个字符,则要将该字符
取下标记 - 步骤:
//主方法,判断有无target单词
for{
for{
if(dfs == true){
return true;
}
}
}
//副方法,dfs
//若为最后一位,相同则直接返回true
if(begin == len-1){
return [][] == [][];
}
//若不为最后一位,则要再次判断,是符合题意的字符则要标记
if([][] == [begin]){
visited == true;
//for循环寻找下一个字符
for{
//对下一个字符进行边界和标记判断
if(inArea && visited[next] == false){
//若未被标记且在区域内,调用dfs
if(dfs(next) == true){
return true;
}
}
}
//四周都没找到,需要对标记过的字符取下标记
visited == false;
}
- 主从函数都需要的变量,可以设为成员变量
- 问是否的问题,可以多尝试布尔类型
- 回溯就是多加了一个回收状态的判断。这点需要仔细体会
130 被围绕的区域M
- 题意:mxn矩阵,被X或O填充,将其中被X围起来的O变为X(在边界上的O,和与该O相连的O不被更改)
- 解法:设置一个状态数组记录状态,从四条边开始dfs,找到O和与其相连的O,将其状态置为true。最终将所有状态为false的O置为X。
934 最短的桥M
- 题意:二维数组存在两个岛,岛四周都是1,其他都是0。找出两个岛之间的最短距离
- 解法:dfs第一个岛并染色为2,状态置为true。bfs另一个岛,一旦找到必定输出的是最短距离。
//广度优先搜索
while(!board.isEmpty()){
int size = board.size(); //先设置第一遍搜索的最大值
for(int i = 0; i < size; ++i){ //开始搜索
int[] next = board.poll();
for(int[] di : dirs){
int nx = next[0] + di[0];
int ny = next[1] + di[1];
if(nx >= 0 && ny >= 0 && nx < m && ny < n && !visited[nx][ny]){
visited[nx][ny] = true; //未被标记过,则标记
if(grid[nx][ny] == 1){ //若为1,则找到了第二个岛,直接返回距离
return res;
}else{ //若不为1,则加入队列,等待下一轮广度搜索
board.add(new int[]{nx, ny});
}
}
}
}
res++;
}
51 N皇后H
- 题意:n*n矩阵,放入n个皇后,按每行一个字符串的形式,输出最终的棋盘。
- 解法:逐行遍历。找到能放入皇后的位置,即列上没有冲突,右上45度没有冲突,左上45度没有冲突,将该位置的.改为Q。直到最后一行。一旦无法找到能合适放入皇后的位置,则进行回溯操作,将上一行的皇后Q置为
.。
//将每一行数组用'.'填满
for(char[] c : board)
Arrays.fill(c, '.');
//判断列时进行剪枝处理
for(int i = 0; i < row; ++i){ //i < row
if(position[i][col] == 'Q'){
return false;
}
}
//res.add(Array2List(position);
public List<String> Array2List(char[][] position){
List<String> tmp = new ArrayList<>();
for(char[] c : position){
tmp.add(String.valueOf(c)); //按行作为字符串插入tmp中。
}
return tmp;
}
257 二叉树的所有路径M
- 题意:给定二叉树,输出每条路径的字符串,"1->2->5","1->3"
- 解法:把节点分为叶子节点和非叶子节点,对非叶子节点使用dfs
- 每次dfs创建新的字符串
public void dfs(TreeNode cur, String path, List<String> res){
//该节点存在,则
if(cur != null){
//定义新字符串,不在原字符串上更改
StringBuilder sb = new StringBuilder(path);
sb.append(Integer.toString(cur.val));
//当前节点非叶子节点
if(cur.left != null || cur.right != null){
sb.append("->");
dfs(cur.left, sb.toString(), res);
dfs(cur.right, sb.toString(), res);
}
//当前节点是叶子节点
else{
res.add(sb.toString());
return;
}
}
}
+ 每次dfs后进行回溯操作,去除"->"
```java
private void dfs(TreeNode root, StringBuilder cur, List<String> paths) {
if (root == null) return;
if (root.left == null && root.right == null) {
paths.add(cur.toString() + root.val);
return;
}
int sz = cur.length();
cur.append(root.val).append("->");
dfs(root.left, cur);
dfs(root.right, cur);
cur.delete(sz, cur.length());
}
- bfs
Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
Queue<String> pathQueue = new LinkedList<String>();
//根节点入队列
nodeQueue.offer(root);
pathQueue.offer(Integer.toString(root.val));
while(!nodeQueue.isEmpty()){
//出队
TreeNode node = nodeQueue.poll();
String path = pathQueue.poll();
//叶子节点
if(node.left == null && node.right == null){
res.add(path);
}
//非叶子节点
if(node.left != null){
nodeQueue.offer(node.left);
pathQueue.offer(new StringBuffer(path).append("->").append(node.left.val).toString());
}
if(node.right != null){
nodeQueue.offer(node.right);
pathQueue.offer(new StringBuffer(path).append("->").append(node.right.val).toString());
}
}

浙公网安备 33010602011771号