一、优先队列的自定义排序
以下是降序:[4,3,2,1,0]
PriorityQueue<Integer> q=
new PriorityQueue<Integer>((o1,o2)->{return o2.compareTo(o1);});
二、回溯
给定一个可包含重复数字的整数集合 nums
,按任意顺序 返回它所有不重复的全排列。
示例 1:
输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]
class Solution {
boolean[] vis;
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> perm = new ArrayList<Integer>();//一个填充完毕的list
vis = new boolean[nums.length];
Arrays.sort(nums);//为了使相同的数字排列在一起
backtrack(nums, ans, 0, perm);
return ans;
}
public void backtrack(int[] nums, List<List<Integer>> ans, int idx, List<Integer> perm) {
if (idx == nums.length) {
ans.add(new ArrayList<Integer>(perm));
return;
}
for (int i = 0; i < nums.length; i++) {
if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && vis[i - 1])) continue;//去重,i-1和i相等,vis[i - 1]表示填过一次了,直接跳过
perm.add(nums[i]);
vis[i] = true;
backtrack(nums, ans, idx + 1, perm);
vis[i] = false;
perm.remove(idx);
}
}
}
三、位运算
1.理解位运算符:
&
:按位与,两个位都为1时结果为1,否则为0。|
:按位或,两个位中有一个为1时结果为1。^
:按位异或,两个位相异时结果为1。~
:按位取反,1变0,0变1。<<
:左移,将位向左移动指定的位数。>>
:右移,将位向右移动指定的位数,高位补符号位(负数补1,正数补0)。>>>
:无符号右移,高位总是补0。
2.掌握位操作的技巧:
- 置位(Set a bit):使用或操作来将特定位置为1。例如,
x | (1 << n)
将x的第n位设为1。 - 清零(Clear a bit):使用与操作和取反来将特定位置为0。例如,
x & ~(1 << n)
将x的第n位清零。 - 切换位(Toggle a bit):使用异或操作来切换位的状态。例如,
x ^ (1 << n)
将x的第n位进行切换。 - 检查位(Check a bit):使用与操作来检查位是否为1。例如,
(x & (1 << n)) != 0
用来检查x的第n位是否为1。
3.利用位运算解决问题:
- 单一数字问题:使用异或操作找到唯一出现的数字,因为相同的数字异或会变成0。
- 计算不同的位数:两个数字异或后,统计结果中1的个数来找出不同的位。
- 幂运算的快速计算:通过位运算可以高效地计算2的幂次方。
4.位运算的实用技巧:
- 位掩码:使用位运算来处理集合,例如,用一个整数的各个位表示集合中元素的存在与否。
- 位运算的优先级:位运算符的优先级较低,进行位运算时经常需要使用括号来确保运算的顺序。
5.优化技巧:
- 使用位运算通常比其他数学运算更快,因为它们直接在数的二进制表示上操作。
- 在实现算法时考虑位运算可以简化问题,比如通过位运算快速计算乘除以2的幂。
四、词频统计
需要统计两个字符串是否相等时
int[] cnt=new int[200];
cnt[s.charAt(i)-' '];
//判断相等
Arrays.equals(cnt1, cnt2);
//需要哈希时
Map<String,>
五、排序+二分查找(分治)
1.快排
(1)递归:
public void sort(int []arr, int left, int right) {
int privot;
if(left<right) {
privot = QuickSort(arr, left, right);
sort(arr, left, privot-1);
sort(arr, privot+1, right);
}
}
public int QuickSort(int []arr, int left, int right) {
int pivot = arr[left];
while(left < right){
while(left < right && pivot >= arr[right]){
right--;
}
arr[left] = arr[right];
while(left < right && pivot <= arr[left]){
left++;
}
arr[right] = arr[left];
}
arr[left] = pivot;
return left;
}
先确定一个数的位置,再分治继续排,确定剩余的数
基准(pivot)为15,r移动到小于15的第一个数,交换,l开始移动,到大于
移动到LR相等,填入val值
分治递归实现
(2)非递归:
public void sort(int []arr, int left, int right) {
int privot, top, last;
Stack<Integer> s = new Stack<Integer>();
last=0;
top=0;
privot=QuickSort(arr, left, right);
if(privot>left+1) {
s.push(left);
s.push(privot-1);
}
if(privot<right-1) {
s.push(privot+1);
s.push(right);
}
while(!s.empty()) {
top = s.pop();
last = s.pop();
privot = QuickSort(arr, last, top);
if(privot>last+1) {
s.push(last);
s.push(privot-1);
}
if(privot<top-1) {
s.push(privot+1);
s.push(top);
}
}
}
public int QuickSort(int []arr, int left, int right) {
int pivot = arr[left];
while(left < right){
while(left < right && pivot >= arr[right]){
right--;
}
arr[left] = arr[right];
while(left < right && pivot <= arr[left]){
left++;
}
arr[right] = arr[left];
}
arr[left] = pivot;
return left;
}
使用栈来模拟递归调用的行为,通过将分区的边界保存在栈中来控制排序过程。每次从栈中取出一个分区并对其进行排序,然后将生成的子分区再次放回栈中,直到栈为空,表示排序完成。
六、双指针
1.双指针类型:
快慢指针:主要用于解决链表中的问题,如检测循环、找到中间节点等。
左右指针:主要应用于排序数组或字符串,用于双向收敛搜索,如二分查找、对撞指针等。
2.边界条件处理:
在使用双指针技巧时,正确处理边界条件至关重要。确保指针移动不会越界,并且要考虑到空数组或空链表的特殊情况。
3.优化搜索过程:
在某些问题中,如果数组已经排序,可以利用双指针技巧有效减少搜索空间,比如在求和、查找对等问题中利用对撞指针。
七、结点
操作原始结点时要注意next,left,right指向哪里,用栈、队列进行操作时记得将其设置为null,或者每次新建结点, 但是新建结点会MLE。
八、前缀和
九、树(深搜广搜)
例题:给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
1.广度优先
class Solution {
public List<Integer> largestValues(TreeNode root) {
if (root == null)
return new ArrayList<Integer>();
List<Integer> res = new ArrayList<Integer>();
Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
int len = queue.size();//当前len确保了len--到0时,刚好处理完当前层
int maxVal = Integer.MIN_VALUE;
while (len > 0) {
TreeNode t = queue.poll();
len--;
maxVal = Math.max(maxVal, t.val);
if (t.left != null)
queue.offer(t.left);
if (t.right != null)
queue.offer(t.right);
}
res.add(maxVal);
}
return res;
}
}
2.深度优先
class Solution {
public List<Integer> largestValues(TreeNode root) {
if (root == null)
return new ArrayList<Integer>();
List<Integer> res = new ArrayList<Integer>();
dfs(res, root, 0);
return res;
}
public void dfs(List<Integer> res, TreeNode root, int curHeight) {
if (curHeight == res.size()) //到新的一层,加进来第一个值
res.add(root.val);
else
res.set(curHeight, Math.max(res.get(curHeight), root.val));
if (root.left != null)
dfs(res, root.left, curHeight + 1);
if (root.right != null)
dfs(res, root.right, curHeight + 1);
}
}
十、贪心
十一、动态规划
凡涉及到空间复杂度过高,可以考虑采用滚动的方法解决
例题:亮灯,每个灯可亮黄色或者蓝色,有对应的得分,求最高得分
private static int calculateMaxScore(int[][] scores, int n) {
int[][] dp = new int[n][3];
// 初始化第一个灯的得分
dp[0][0] = 0; // 第一个灯不亮
dp[0][1] = scores[0][0]; // 第一个灯亮蓝色
dp[0][2] = scores[0][1]; // 第一个灯亮黄色
// 动态规划
for (int i = 1; i < n; i++) {
// 当前灯不亮,取前一个灯的最大得分
dp[i][0] = Math.max(Math.max(dp[i-1][0], dp[i-1][1]), dp[i-1][2]);
// 当前灯亮蓝色,前一个灯可以不亮或者亮黄色
dp[i][1] = scores[i][0] + Math.max(dp[i-1][0], dp[i-1][2]);
// 当前灯亮黄色,前一个灯可以不亮或者亮蓝色
dp[i][2] = scores[i][1] + Math.max(dp[i-1][0], dp[i-1][1]);
}
// 最后一个灯可以是蓝色或黄色,或者不亮,取三者的最大值
return Math.max(Math.max(dp[n - 1][0], dp[n - 1][1]), dp[n - 1][2]);
}
十二、图(深搜广搜)
1.深度优先
例题:求岛屿最大面积,0为水,1为陆地
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int ans=0;
for(int i=0;i<grid.length;i++) {
for(int j=0;j<grid[0].length;j++)
ans=Math.max(ans, dfs(grid,i,j));
}
return ans;
}
public int dfs(int[][] grid,int i, int j) {
if(i==-1||j==-1||i==grid.length||j==grid[0].length||grid[i][j]==0) return 0;
grid[i][j]=0;
int[] di= {0,0,1,-1};
int[] dj= {1,-1,0,0};
int ans=1;
for(int index=0;index<4;index++)
ans+=dfs(grid,i+di[index],j+dj[index]);
return ans;
}
}
十三、单调栈
例题:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0
来代替。
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int[] ans = new int[temperatures.length];
Deque<Integer> stack = new ArrayDeque<Integer>();
for (int i = 0; i < length; i++) {
int temperature = temperatures[i];
while (!stack.isEmpty() && temperature > temperatures[stack.peek()]) {
int prevIndex = stack.pop();
ans[prevIndex] = i - prevIndex;
}
stack.push(i);
}
return ans;
}
}
单调栈思想,栈中存储下标,栈中数据所代表的温度从栈底到栈顶是从高到低的,从前向后遍历原始数组。栈不空时,当有温度元素大于栈顶元素时,取出栈顶元素,更新ans[栈内index](代表的是还需要几天才升高)=当前index-栈内index;温度小于栈顶或者栈空时,直接入栈,最后将所有的栈内残留ans[index]=0。