算法通用模板

一、优先队列的自定义排序

以下是降序:[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。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值