剑指offer思路笔记

剑指offer

  • 剑指 Offer 62. 圆圈中最后剩下的数字
    • 解法一:模拟删除数字,O(mn)
    • 解法二:约瑟夫环,从最后向前找索引,O(n)
  • 剑指 Offer 38. 字符串的排列
    • 解法一:全排列,回溯法:在DFS中使用for循环,调换idx与之后的idx的数据:O(n!)O(n2)
  • 剑指 Offer 24. 反转链表
    • 解法一:遍历链表,存储逆序O(n)O(n)
    • 解法二:双指针O(n)O(1)
    • 解法二:递归O(n)
  • 剑指 Offer 51. 数组中的逆序对
    • 解法一:暴力,O(n2)O(1)
    • 解法二:归并排序过程中统计逆序对,O(nlogn)O(n)
  • 剑指 Offer 56 - I. 数组中数字出现的次数
    • 解法一:暴力,一遍HashMap计数,一遍搜索,O(n)O(n)
    • 解法二:异或;O(n)O(1)
  • 剑指 Offer 09. 用两个栈实现队列
    • 解法一:模拟就完事了 最坏O(n)最好O(1) 平均O(1)
  • 剑指 Offer 29. 顺时针打印矩阵
    • 解法一:模拟:设置可到达的边界,循环里四个方向依次走到底,判断边界跳出即可 O(mn)
  • 剑指 Offer 46. 把数字翻译成字符串
    • 解法一:暴力递归:O(2n) ,O(n)
      • 手下dfs(idx+1);下标idx,遇到1就有dfs(idx+2);遇到2有idx+1的情况下,如果idx+1<=5,那么dfs(idx+2)
    • 解法二:DP O(n),O(n)
  • 剑指 Offer 40. 最小的k个数
    • 解法一:计数排序:O(n),O(数的范围)
    • 解法二:大根堆:O(nlogk),O(k)
    • 解法三:快排:O(nlogk),O(k)
        • 原始快排算法:
        • TopK快排算法:
  • 剑指 Offer 42. 连续子数组的最大和
    • 解法一:暴力求和:O(n3),O(1)
    • 解法er:动态规划:O(n),O(n)/O(1)
      • 朴素解法:空间O(n)
      • 状态压缩:空间O(1)
  • 剑指 Offer 10- II. 青蛙跳台阶问题
    • 解法一:暴力递归O(2n)
    • 解法二:递归加备忘录
    • 解法三:动态规划+状态压缩
  • 剑指 Offer 60. n个骰子的点数
    • 解法一:暴力递归:O(6n)超时
    • 解法二:动态规划
      • 朴素解法
      • 状态压缩
  • 热题:leetcode253. 会议室 II
    • 解法一:优先队列
    • 解法二:上车下车问题,统计同一时间内的最大人数:
  • 热题:leetcode351. 安卓系统手势解锁
    • 解法一:带状态的DFS

剑指 Offer 62. 圆圈中最后剩下的数字

解法一:模拟删除数字,O(mn)

    public int lastRemaining(int n, int m) {
		构造list
        int idx = 0;
        while (n > 1) {
            idx = (idx + m - 1) % n;
            list.remove(idx);
            n--;
        }
        return list.get(0);
    }

解法二:约瑟夫环,从最后向前找索引,O(n)

   public int lastRemaining(int n, int m) {
      int ans=0;   最后的下标必然等于0
         for (int i=2;i<=n;i++){
          ans=(ans+m)%i;  i是倒数最后一次删除前的数据size
        }
        return ans;
    }

剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

解法一:全排列,回溯法:在DFS中使用for循环,调换idx与之后的idx的数据:O(n!)O(n2)

题解链接.

class Solution {
    List<String> res = new LinkedList<>();
    char[] c;
    public String[] permutation(String s) {
        c = s.toCharArray();
        dfs(0);
        return res.toArray(new String[res.size()]);
    }
    void dfs(int x) {
        if(x == c.length - 1) {
            res.add(String.valueOf(c));  添加排列方案
            return;
        }
        HashSet<Character> set = new HashSet<>();
        for(int i = x; i < c.length; i++) {
            if(set.contains(c[i])) continue; 重复,因此剪枝
            set.add(c[i]); 
            swap(i, x);  交换,将 c[i] 固定在第 x 位 
            dfs(x + 1);   开启固定第 x + 1 位字符
            swap(i, x);   恢复交换
        }
    }
    void swap(int a, int b) {
        char tmp = c[a];
        c[a] = c[b];
        c[b] = tmp;
    }
}

剑指 Offer 24. 反转链表

解法一:遍历链表,存储逆序O(n)O(n)

解法二:双指针O(n)O(1)

    public ListNode reverseList(ListNode head) {
        ListNode pre=null,nxt;   定义pre和nxt
        while(head!=null){
            nxt=head.next;   保存下一节点  
            head.next=pre;    改变cur.next指向
            pre=head;         更新pre为cur
            head=nxt;         更新cur为nxt
        }
        return pre;

    }

解法二:递归O(n)

class Solution {
	public ListNode reverseList(ListNode head) {
		//递归终止条件是当前为空,或者下一个节点为空
		if(head==null || head.next==null) {
			return head;
		}
		//这里的cur就是最后一个节点
		ListNode cur = reverseList(head.next);
		//这里请配合动画演示理解
		//如果链表是 1->2->3->4->5,那么此时的cur就是5
		//而head是4,head的下一个是5,下下一个是空
		//所以head.next.next 就是5->4
		head.next.next = head;
		//防止链表循环,需要将head.next设置为空
		head.next = null;
		//每层递归函数都返回cur,也就是最后一个节点
		return cur;
	}
}

剑指 Offer 51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

输入: [7,5,6,4]
输出: 5

解法一:暴力,O(n2)O(1)

两层循环暴力比较并计数,面试官会问你优化

解法二:归并排序过程中统计逆序对,O(nlogn)O(n)

https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/shu-zu-zhong-de-ni-xu-dui-by-leetcode-solution/

class Solution {
    public int reversePairs(int[] nums) {
        if (nums.length<2){
            return 0;
        }
        int[] tmp=new int[nums.length];
        
        求区间0-nums.length的数组的逆序对
        int ans=reversePairs(nums,0,nums.length-1,tmp);

		nums已经被排好序,如果不能改变原数组,则需要开始时候拷贝
        for(int i:nums){
            System.out.print(i+" ");
        }
        return ans;
    }
	
	求区间0-nums.length的数组的逆序对,
	前提:左边排好序,右边排好序,
    private int reversePairs(int[] nums,int left,int right,int[] tmp){
        if (left==right){
            return 0;
        }
        
        递归中的递:不断分解子问题
        int mid=left+(right-left)/2;
        int leftNum=reversePairs(nums,left,mid,tmp);
        int rightNum=reversePairs(nums,mid+1,right,tmp);
        
        递归中的归:合并时累加逆序对
        
        优化:左边最大值<右边最小值:合并过程中不产生交叉的逆序对
        if (nums[mid]<nums[mid+1]){
			return leftNum+rightNum;
		}
        int crossNum=mergePairs(nums,left,mid,right,tmp);
        return leftNum+rightNum+crossNum;
    }
    
    合并数组时中累加逆序对数
    private int mergePairs(int[] nums,int left,int mid,int right,int[] tmp){
    tmp暂存,每次需要拷贝当前的数据
    nums是最终归并后的数据
        for(int i=left;i<=right;i++){
            tmp[i]=nums[i];
        }
        int i=left;
        int j=mid+1;
        int crossCnt=0;
        for (int k=left;k<=right;k++){
        判断i和j的边界
            if (i>=mid+1){
                nums[k]=tmp[j];
                j++;
            }else if(j>=right+1){
                nums[k]=tmp[i];
                i++;
            }else if(tmp[i]<=tmp[j]){
                nums[k]=tmp[i];
                i++;
            }else{
                nums[k]=tmp[j];  
                crossCnt+=mid-i+1;
                j++;
            }
        }
        return crossCnt;
    }
}

剑指 Offer 56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法一:暴力,一遍HashMap计数,一遍搜索,O(n)O(n)

解法二:异或;O(n)O(1)

  • 相同的数异或为0:可用于找一个只出现奇数次的数
  • 对于两个出现奇数次的数组,可以一遍异或,根据异或结果提取一个bit可以用于区分这两个数据,进而将所有数据分为两组,并且相同的数分在一组,这样就转换为上述结论了
class Solution {
    public int[] singleNumbers(int[] nums) {
        int xor=0;
        for (int i:nums){
            xor^=i;
        }
        int mask=1;
        while((xor&mask)==0){
            mask=mask<<1;
            //mask<<=1;
        }
        int ans1=0,ans2=0;
        for (int i:nums){
            if ((i&mask)==0){
                ans1^=i;
            }else{
                ans2^=i;
            }
        }
        return new int[]{ans1,ans2};

    }
}

剑指 Offer 09. 用两个栈实现队列

解法一:模拟就完事了 最坏O(n)最好O(1) 平均O(1)

class CQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;
    
    public CQueue() {
        this.stack1 = new Stack<>();
        this.stack2 = new Stack<>();

    }
    public void appendTail(int value) {
        stack1.add(value);
    }

    public int deleteHead() {
        if (stack2.isEmpty()){
            if (stack1.isEmpty()){
                return -1;
            }else{
                while (!stack1.isEmpty()){
                    stack2.add(stack1.pop());
                }
            }
        }
        return stack2.pop();
    }
}

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */

剑指 Offer 29. 顺时针打印矩阵

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

解法一:模拟:设置可到达的边界,循环里四个方向依次走到底,判断边界跳出即可 O(mn)

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length == 0) return new int[0];
        int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;
        int[] res = new int[(r + 1) * (b + 1)];
        while(true) {
            for(int i = l; i <= r; i++) res[x++] = matrix[t][i];  left to right.
            if(++t > b) break;
            for(int i = t; i <= b; i++) res[x++] = matrix[i][r];  top to bottom.
            if(l > --r) break;
            for(int i = r; i >= l; i--) res[x++] = matrix[b][i];  right to left.
            if(t > --b) break;
            for(int i = b; i >= t; i--) res[x++] = matrix[i][l];  bottom to top.
            if(++l > r) break;
        }
        return res;
    }
}

剑指 Offer 46. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法一:暴力递归:O(2n) ,O(n)

手下dfs(idx+1);下标idx,遇到1就有dfs(idx+2);遇到2有idx+1的情况下,如果idx+1<=5,那么dfs(idx+2)

class Solution {
    private int cnt=0;
    private String numStr;
    public int translateNum(int num) {
        this.numStr=String.valueOf(num);
        dfs(0);
        return cnt;
    }
    private void dfs(int idx){
        if (idx==numStr.length()){
            cnt++;
            return;
        }else if(idx>numStr.length()){
            return;
        }
        dfs(idx+1);
        if (numStr.charAt(idx)=='1'){
            dfs(idx+2);
        }else if(numStr.charAt(idx)=='2' && idx<numStr.length()-1 && numStr.charAt(idx+1)<='5'){
            dfs(idx+2);
        }
    }
}

解法二:DP O(n),O(n)

剑指offer思路笔记_第1张图片

class Solution {
    public int translateNum(int num) {
        String s = String.valueOf(num);
        int[] dp = new int[s.length()+1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= s.length(); i ++){
            String temp = s.substring(i-2, i);
            if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0)
                dp[i] = dp[i-1] + dp[i-2];
            else
                dp[i] = dp[i-1];
        }
        return dp[s.length()];
    }
}

剑指 Offer 40. 最小的k个数

示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:

0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法一:计数排序:O(n),O(数的范围)

 public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        统计每个数字出现的次数
        int[] counter = new int[10001];
        for (int num: arr) {
            counter[num]++;
        }
        根据counter数组从头找出k个数作为返回结果
        int[] res = new int[k];
        int idx = 0;
        for (int num = 0; num < counter.length; num++) {
            while (counter[num]-- > 0 && idx < k) {
                res[idx++] = num;
            }
            if (idx == k) {
                break;
            }
        }
        return res;
    }
  • 数据范围比较大,但是种类不多的时候,使用TreeMap来存储
import java.util.*;

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        TreeMap<Integer, Integer> map = new TreeMap<>();
        for (int i = 0; i < arr.length; i++) {
            if (map.containsKey(arr[i])) {
                map.put(arr[i], map.get(arr[i]) + 1);
            } else {
                map.put(arr[i], 1);
            }
        }
        int cnt = 0;
        int[] ans=new int[k];
        for (Map.Entry<Integer,Integer> entry : map.entrySet()) {
            int min=Math.min(k-cnt,entry.getValue());
            for (int i = 0; i < min; i++) {
                ans[cnt++]=entry.getKey();
            }
        }
        return ans;
	}
}

解法二:大根堆:O(nlogk),O(k)

public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        默认是小根堆,实现大根堆需要重写一下比较器。
        Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
        for (int num: arr) {
            if (pq.size() < k) {
                pq.add(num);
            } else if (num < pq.peek()) {
                pq.poll();
                pq.add(num);
            }
        }
        返回堆中的元素
        int[] res = new int[pq.size()];
        int idx = 0;
        for(int num: pq) {
            res[idx++] = num;
        }
        return res;
    }

解法三:快排:O(nlogk),O(k)

原始快排算法:

  1. 定义包含区间长度的快排,如果满足l
  2. 分治:使用l作为base,从l+1向r比较值大小,如果小于base的值,则交换到idx(idx默认从l+1开始),然后idx++,交换idx-1与base值,此时切分值左边都是比他小的,右边都是比他大的,返回此时idx-1就是base下标
class MySolution {

    public void quickSort(int[] arr) {
        quickSort(arr,0,arr.length-1);
    }

    private void quickSort(int[] arr,int l,int r){
        if (l<r){
            int base = partition(arr, l, r);
            quickSort(arr,l,base-1);
            quickSort(arr,base+1,r);
        }
    }
    private int partition(int[] arr, int l, int r){
        int base =l;      定义基准值为左边的值
        int idx=base+1;   定义最终返回的基准值的下标
        for (int i = idx; i <=r; i++) {
            if (arr[i]<arr[base]){
                swap(arr,i,idx);
                idx++;    下标右移,表示找到一个比基准值小的数
            }
        }
        交换基准值与idx-1的值,使得基准值所处位置正确
        swap(arr,base,idx-1);
        return idx-1;
    }
    private void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
}

TopK快排算法:

public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }else if(k==arr.length){
            return arr;
        }
        int base=quickSort(arr,0,arr.length-1,k);
        return Arrays.copyOf(arr, base);

    }
    private int quickSort(int[] arr,int l,int r,int k){

            int base =partition(arr,l,r);
            if (base==k){
                return base;
            }
            base>k 继续划分左区间,反之,继续划分右区间
            return base>k? quickSort(arr,l,base-1,k):quickSort(arr,base+1,r,k);

    }

   
    private int partition(int[] arr, int left, int right) {
        // 设定基准值(pivot)
        int pivot = left;
        int index = pivot + 1;
        for (int i = index; i <= right; i++) {
            if (arr[i] < arr[pivot]) {
                swap(arr, i, index);
                index++;
            }
        }
        swap(arr, pivot, index - 1);
        return index - 1;
    }

    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

剑指 Offer 42. 连续子数组的最大和

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

解法一:暴力求和:O(n3),O(1)

遍历下标,遍历子数组长度,遍历子数组求和:超时

    public int maxSubArray(int[] nums) {
        int maxSum=Integer.MIN_VALUE;
        for(int i=0;i<nums.length;i++){
            for(int l=1;l<=nums.length-i;l++){
                int curSum=0;
                for (int j = 0; j < l; j++) {
                    curSum+=nums[i+j];
                }
                maxSum=Math.max(maxSum,curSum);
            }
        }
        return maxSum;
    }

解法er:动态规划:O(n),O(n)/O(1)

朴素解法:空间O(n)

剑指offer思路笔记_第2张图片

    public int maxSubArray(int[] nums) {
        int[] dp=new int[nums.length];
        dp[0]=nums[0];
        int ans=dp[0];
        for(int i=1;i<nums.length;i++){
            if (dp[i-1]>0){
                dp[i]=dp[i-1]+nums[i];
            }else{
                dp[i]=nums[i];
            }
            ans=Math.max(ans,dp[i]);
        }
        return ans;
    }

状态压缩:空间O(1)

由于dp[i]=dp[i-1]+nums[i];
dp[i]只与dp[i-1]相关,因此可以用两个变量储存即可

    public int maxSubArray(int[] nums) {
        int a=nums[0];
        int ans=a;
        int b;
        for(int i=1;i<nums.length;i++){
            if (a>0){
                b=a+nums[i];
            }else{
                b=nums[i];
            }
            ans=Math.max(ans,b);
            这里a的值更新
            a=b;
        }
        return ans;
    }

判断条件优化,让代码更加优雅

 if (a>0){
     b=a+nums[i];
 }else{
     b=nums[i];
 }
可以替换为
b=Math.max(0,a)+nums[i];

更进一步,使用原来的数组nums缓存数据,可以只使用一个int变量

    public int maxSubArray(int[] nums) {
        int res = nums[0];
        for(int i = 1; i < nums.length; i++) {
            nums[i] += Math.max(nums[i - 1], 0);
            res = Math.max(res, nums[i]);
        }
        return res;
    }

剑指 Offer 10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法一:暴力递归O(2n)

暴力递归深度;

public int numWays(int n) {
        if (n<2){
            return 1;
        }
        else if(n==2){
        	return 2;
        }
        return numWays(n-1)+numsWays(n-2);
    }

解法二:递归加备忘录

class Solution {
    private int[] arr;
    public int numWays(int n) {
        if (n<2){
            return 1;
        }
        this.arr=new int[n+1];
        arr[0]=1;
        arr[1]=1;
        arr[2]=2;
        return helper(n);
    }
    private int helper(int n){
        if(n<=2 || arr[n]!=0){
            return arr[n];
        }
        arr[n-1]=helper(n-1)%(1000000007);
        arr[n-2]=helper(n-2)%(1000000007);
        arr[n]= (arr[n-1]+arr[n-2])%(1000000007);
        return arr[n];
    }
}

解法三:动态规划+状态压缩

状态转移方程其实已经在递归代码中写出来了
朴素DP

    public int numWays(int n) {
        if (n<2){
            return 1;
        }
        int[] dp= new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=(dp[i-1]+dp[i-2])%(1000000007);
        }
        return dp[n];
    }

状态压缩

class Solution {
    public int numWays(int n) {
        int a = 1, b = 1, sum;
        for(int i = 0; i < n; i++){
            sum = (a + b) % 1000000007;
            a = b;
            b = sum;
        }
        return a;
    }
}

剑指 Offer 60. n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

解法一:暴力递归:O(6n)超时

剑指offer思路笔记_第3张图片

解法二:动态规划

朴素解法

dp[i][j] ,表示投掷完 i枚骰子后,点数 j 的出现次数。
转移方程及边界条件
剑指offer思路笔记_第4张图片

    public double[] twoSum(int n) {
        int[][] dp=new int[12][67];
        初始化
        for(int i=1;i<=6;i++){
            dp[1][i]=1;
        }
        for(int i=2;i<=11;i++){
            for(int j=i;j<=6*i;j++){
                //种类求和
                for(int k=1;k<=6;k++){
                    if(j-k<=0){
                        break;
                    }
                    dp[i][j]+=dp[i-1][j-k];
                }
            }
        }
        double[] ans=new double[1+5*n];
        计算概率
        double all=Math.pow(6,n);
        for(int i=n;i<=6*n;i++){
            ans[i-n]=dp[n][i]*1.0/all;
        }
        return ans;
    }

状态压缩

用概率直接计算,并压缩状态

    public double[] twoSum(int n) {
        double pre[]={1/6d,1/6d,1/6d,1/6d,1/6d,1/6d};
        for(int i=2;i<=n;i++){
            double tmp[]=new double[5*i+1];
            for(int j=0;j<pre.length;j++)
                for(int x=0;x<6;x++)
                    tmp[j+x]+=pre[j]/6;
            pre=tmp;
        }
        return pre;
    }

作者:zhi-xiong
链接:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/solution/java-dong-tai-gui-hua-by-zhi-xiong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

热题:leetcode253. 会议室 II

给定一个会议时间安排的数组,每个会议时间都会包括开始和结束的时间 [[s1,e1],[s2,e2],…] (si < ei),为避免会议冲突,同时要考虑充分利用会议室资源,请你计算至少需要多少间会议室,才能满足这些会议安排。

示例 1:

输入: [[0, 30],[5, 10],[15, 20]]
输出: 2

解法一:优先队列

class Solution {
    public int minMeetingRooms(int[][] intervals) {
    if (intervals.length == 0) {
      return 0;
    }
    Min heap
    PriorityQueue<Integer> allocator =new PriorityQueue<Integer>(intervals.length,(o1,o2)->o1-o2);

    Arrays.sort(intervals,(o1,o2)->o1[0]-o2[0]);
	第一个会议先开始,那么他的结束时间加入最小堆
    allocator.add(intervals[0][1]);

    for (int i = 1; i < intervals.length; i++) {
		最小堆的顶部是最先结束的会议时间
      if (intervals[i][0] >= allocator.peek()) {
      另一个会议的开始时间,大于等于最早的会议结束时间,那么这个会议开始
        allocator.poll();
      }
      添加这个会议结束时间
      allocator.add(intervals[i][1]);
    }
    return allocator.size();
  }
}

解法二:上车下车问题,统计同一时间内的最大人数:

class Solution {
    public int minMeetingRooms(int[][] intervals) {
        int[][] data=new int[intervals.length*2][2];
        for (int i = 0; i < intervals.length; i++) {
        将数据分解,时间+操作(开始+1,结束-1)
            data[i*2][0]=intervals[i][0];
            data[i*2][1]=1;
            data[i*2+1][0]=intervals[i][1];
            data[i*2+1][1]=-1;
        }
        排序按照时间先后,其次,对于结束和开始在同一时刻的,结束在前,开始在后,累计时则不会计入房间数
        Arrays.sort(data,((o1, o2) -> {
            if(o1[0]>o2[0]){
                return 1;
            }else if(o1[0]<o2[0]){
                return -1;
            }else{
                return o1[1]-o2[1];
            }
        }));
        int ans=0,cur=0;
        for (int i = 0; i < data.length; i++) {
            // System.out.println(data[i][0]+" "+data[i][1]);
            cur+=data[i][1];
            ans=Math.max(ans,cur);
        }
        return ans;
    }
}

优化一下代码

class Solution {
    public int minMeetingRooms(int[][] intervals) {
		
		int n=intervals.length;
		int[] start=new int[n];
		int[] end = new int[n];
		for (int i = 0; i < n; i++) {
			start[i]=intervals[i][0];
			end[i]=intervals[i][1];
		}
		Arrays.sort(start);
		Arrays.sort(end);
	
		int i=0,j=0,count=0,res=0;
		while(i<n) {
			
			if (start[i]<end[j]) {
				count++;
				i++;
				res=Math.max(count, res);
			}else if (start[i]>end[j]) {
				count--;
				j++;
			}else {
				i++;
				j++;
			}
		}
		return res;
    }
}

热题:leetcode351. 安卓系统手势解锁

我们都知道安卓有个手势解锁的界面,是一个 3 x 3 的点所绘制出来的网格。

给你两个整数,分别为 ​​m 和 n,其中 1 ≤ m ≤ n ≤ 9,那么请你统计一下有多少种解锁手势,是至少需要经过 m 个点,但是最多经过不超过 n 个点的。

先来了解下什么是一个有效的安卓解锁手势:

每一个解锁手势必须至少经过 m 个点、最多经过 n 个点。
解锁手势里不能设置经过重复的点。
假如手势中有两个点是顺序经过的,那么这两个点的手势轨迹之间是绝对不能跨过任何未被经过的点。
经过点的顺序不同则表示为不同的解锁手势。

剑指offer思路笔记_第5张图片

解释:

| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 | 9 |
无效手势:4 - 1 - 3 - 6
连接点 1 和点 3 时经过了未被连接过的 2 号点。

无效手势:4 - 1 - 9 - 2
连接点 1 和点 9 时经过了未被连接过的 5 号点。

有效手势:2 - 4 - 1 - 3 - 6
连接点 1 和点 3 是有效的,因为虽然它经过了点 2 ,但是点 2 在该手势中之前已经被连过了。

有效手势:6 - 5 - 4 - 1 - 9 - 2
连接点 1 和点 9 是有效的,因为虽然它经过了按键 5 ,但是点 5 在该手势中之前已经被连过了。

示例:

输入: m = 1,n = 1
输出: 9

解法一:带状态的DFS

剑指offer思路笔记_第6张图片

剑指offer思路笔记_第7张图片

from functools import lru_cache


class Solution:
    def numberOfPatterns(self, m: int, n: int) -> int:
        不可达的map
        graph = {
            1: {3: 2, 7: 4, 9: 5},
            2: {8: 5},
            3: {1: 2, 7: 5, 9: 6},
            4: {6: 5},
            5: {},
            6: {4: 5},
            7: {1: 4, 3: 5, 9: 8},
            8: {2: 5},
            9: {1: 5, 3: 6, 7: 8},
        }
        ans = 0

        @lru_cache(None)
        def dfs(status, current, count):
            if count == n:
                return 1
            current_ans = 0 if count < m else 1
            for i in range(1, 10):
                if status & (1 << i) == 0:
                    if i not in graph[current] or ((1 << graph[current][i]) & status):
                        current_ans += dfs(status | (1 << i), i, count + 1)
            return current_ans

        # for cur in range(1, 10):
            # ans += dfs(1 << cur, cur, 1)
        
        ans += 4 * dfs(1 << 1, 1, 1)
        ans += 4 * dfs(1 << 2, 2, 1)
        ans += dfs(1 << 5, 5, 1)
        return ans

你可能感兴趣的:(java)