七月算法笔记

动态规划

动态规划解决的问题有
背包问题 01背包 多重背包 完全背包问题
打家劫舍
股票问题
子序列问题

动态规划的本质性解题步骤

1.dp数组的含义,以及下标的含义
//到底是定义一维的dp数组 还是二维的dp数组
求子序列 求背包 二维数组
i j都是什么意思?
dp[i][j];
2.递推公式 阅读题目,寻找关系。 //理解题目的大概意思

3.dp数组初始化  //这个好难,dp数组的初始化。

4.dp数组遍历顺序(关键)

5.打印dp数组

滚动数组

dp[i][j]=max(dp[i-1][j-weight[i]]+value[i],dp[i-1][j]);
//当前层是由上一层推到出来的
// 滚动数组将二维数组转为一维数组
//当前层是由上一层拷贝而来的,将上一层拷贝给当前层,把新的层覆盖到当前层
dp[j] 容量为j的背包能装的最大价值是多少?
一维dp数组
dp[j]=max(dp[j]//不妨物品i,将上一层数据拷贝下来了,dp[j-wegiht[i]+value[i]]);
//初始化
 dp[0]=0; 背包容量为0的时候,装的价值为0;
//遍历顺序
 一维数组遍历的顺序 
          for()
          for(倒叙遍历)j=weight[i],j>=weight[i],j--;
 //保证物品只添加了一次
  //一维dp数组,每次重复利用的,比较讲究遍历的顺序。
 
  //

0-1背包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mZ32HGZR-1691505556706)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230708163034444.png)]

class Solution {
    //思维转化,求出来的子集它的和等于数组总和的一半,那么求出总和然后再除以2,得到一半,即在这个背包里面找到价值为一半的石头,它的价值和体积是相同的。
    public boolean canPartition(int[] nums) {
      int sum= Arrays.stream(nums).sum();
      //给定一个非空的数组 nums,是否可以将这个数组分割成两个子集
        // 一个数组,里面的元素的值既是体积也是价值
        // 首先求和,然后求出target,target就是最大的价值,这个背包的最大价值
      int target=sum/2;
        //剪枝优化,如果是奇数,那么就直接返回了
      if (sum%2==1){
          return false;
      }
      int dp[]=new int[target+1];
        dp[0]=0;
        for (int i = 0; i < nums.length; i++) {
            //先遍历物品
            for (int j = target; j >=nums[i] ; j--) {
                //重后往前进行遍历,可以找到。

                //每个数都使用一次
                dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
                //把上衣行复制下来的,本质没有改变。
            }
            if(dp[target] == target)
                return true;
        }
        return dp[target] == target;
    }
    //给一个集合,把集合分为两个集合,使得两个子集的元素和相等
    // 元素不能重复使用 01背包问题
}

完全背包问题

零一背包的区别在于每个物品可以使用无数次;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-InjjRy8i-1691505556707)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230714092231195.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h8zHn92t-1691505556708)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230714092638955.png)]

先遍历物品在遍历背包,结果是相同的。

滑动窗口

//算法实现的大致思路
int left=0,right=0;//双指针
while(letf<right&&right<s.size())
   window.add(s[right];
   right++;
    while(window needs shrink){
        winodw.remove(s[left];
            letf++;
    }
算法框架
/* 滑动窗口算法框架 */
void slidingWindow(String s) {
    // 用合适的数据结构记录窗口中的数据
    HashMap<Character, Integer> window = new HashMap<>();

    int left = 0, right = 0;
    while (right < s.length()) {
        // c 是将移入窗口的字符
        char c = s.charAt(right);
        window.put(c, window.getOrDefault(c, 0) + 1);
        // 增大窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        // 注意在最终的解法代码中不要 print
        // 因为 IO 操作很耗时,可能导致超时
        System.out.printf("window: [%d, %d)\n", left, right);
        /********************/

        // 判断左侧窗口是否要收缩
        while (left < right && window needs shrink) {
            // d 是将移出窗口的字符
            char d = s.charAt(left);
            window.put(d, window.get(d) - 1);
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

算法常用模板,直接在上面进行修改

public class 模板 {
    public static void main(String[] args) {

    }
    public static  String minWindow(String s,String t){
        //s是原来的字串,t是目标字串
        Map<Character,Integer> need=new HashMap<>();
        Map<Character,Integer> window=new HashMap<>();
        for (char c:t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
            //API getOrDefault的用法,如果存在返回对应的值,如果不存在返回-1;
        }
        int left=0,right=0; //左指针和右指针
        int valid=0; //有效数字
        int start=0,len=Integer.MAX_VALUE;
        while (right<s.length()){
            char c=s.charAt(right);
            right++;
            if (need.containsKey(c)){
                window.put(c,window.getOrDefault(c,0)+1);
                if (window.get(c).equals(need.get(c))){
                    valid++; //找到满足的数字
                }
            }
            while(valid==need.size()){
                if (right-left<len){
                    start=left;//从left开始
                    len=right-start;
                }

                char d =s.charAt(left);
                left++;
                if (need.containsKey(d)){
                    if (window.get(d).equals(need.get(d))){
                        valid--;
                        window.put(d,window.get(d)-1);
                    }
                }
            }

        }
        return  len==Integer.MAX_VALUE?"":s.substring(start,start+left);
    }
}
//理论上你可以设计两端都开或者两端都闭的区间,但设计为左闭右开区间是最方便处理的。因为这样初始化 left = right = 0 时区间 [0, 0) 中没有元素,但只要让 right 向右移动(扩大)一位,区间 [0, 1) 就包含一个元素 0 了。如果你设置为两端都开的区间,那么让 right 向右移动一位后开区间 (0, 1) 仍然没有元素;如果你设置为两端都闭的区间,那么初始区间 [0, 0] 就包含了一个元素。这两种情况都会给边界处理带来不必要的麻烦。

leetcode 76题最小覆盖字串


leetcode 567 字符串的排列


leetcode 438 找到字符串中所有的字母异位词


java大数

BigInteger 和BigDecimal;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edFGH31t-1691505556708)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230703102133493.png)]

在 java.math 包下面的。

public class 大数 {
    public static void main(String[] args) {
        BigInteger  B1 =new BigInteger("123");
        BigInteger  B2 =new BigInteger("125");

        System.out.println(B1.multiply(B2));
        System.out.println(B1.add(B2));
        System.out.println(B1.max(B2));
        System.out.println(B2.subtract(B1));
        System.out.println(B2.divide(B1));

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qPERDjcT-1691505556709)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230703102905433.png)]

数组

二分法

二分法的原型 有序 搜索一个target 返回对应索引的值 

二分最基础的框架
int binarySearch(int[] nums, int target) {
    int left = 0, right = ...;

    while(...) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}
left + (right - left) / 2 就和 (left + right) / 2 的结果相同,但是有效防止了letf和right太大,直接相加导致溢出的情况。
    

二分查找的模板,以后都采用左闭右闭的思路
基础的查找
    int binary_search(int[] nums, int target) {
    int left = 0, right = nums.length - 1; 
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1; 
        } else if(nums[mid] == target) {
            // 直接返回
            return mid;
        }
    }
    // 直接返回
    return -1;
}

想左搜索,这是由于 12223,如果寻找2,右三个的下标都是2,左寻找,是找到左边界那个
    int left_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定左侧边界
            right = mid - 1;
        }
    }
    // 判断 target 是否存在于 nums 中
    if (left < 0 || left >= nums.length) {
        return -1;
    }
    // 判断一下 nums[left] 是不是 target
    return nums[left] == target ? left : -1;
}
//最后判断 left 是否越界,这个很重要。


//向右进行搜索
int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定右侧边界
            left = mid + 1;
        }
    }
    // 判断 target 是否存在于 nums 中
    // if (left - 1 < 0 || left - 1 >= nums.length) {
    //     return -1;
    // }
    
    // 由于 while 的结束条件是 right == left - 1,且现在在求右边界
    // 所以用 right 替代 left - 1 更好记
    if (right < 0 || right >= nums.length) {
        return -1;
    }
    return nums[right] == target ? right : -1;
}
二分模板题

leetcode 1011

在传送带上面包裹必须在days天内从一个港口运送到另一个港口,给出包裹的质量,包裹是依次运出的,给出天数,在这些天内都运出去,返回days天内将传送带上所有包裹去拿不运出去的最低能力
class Solution {
    public int shipWithinDays(int[] weights, int days) {

        //运输 涉及到赵左边的最小值或者右边的最大值问题,都可以采用二分查找来解决
        //二分 letf 和right是如何确定的呢?
        //days天运出,最多一天,最迟每天云一趟,那么最大承载量就是所有的和
        // 最小的承载力就是最大的包裹的质量
        int left=Arrays.stream(weights).max().getAsInt();
        int right=Arrays.stream(weights).sum(); //求得和

        while (left<right){
            //往左搜索
            int mid =left+(right-left)/2;

            if (isC(weights,days,mid)) {
                //搜索左边界的题目 
                //运的比较多了,我们要往小的进行寻找
                right=mid;
            }else{
                //每天云的太少了
                left=mid+1;
            }
        }
          //不断的往左进行搜索的,结束的条件是left=right
        return  right;

    }
    public  static boolean isC(int weights[],int Days,int H){

        //如何判断呢
        int count=1; //记录当前的天数
      int leiji=0;
        for (int i = 0; i < weights.length; i++) {
            leiji+=weights[i];

            if (leiji>H){
                count++;
                leiji=weights[i];
            }
            if (count>Days){
                return false;//运载量是现实太小了根部运输不了
            }
        }
        return true;
    }
}

leetcode 875爱吃香蕉

有n堆香蕉,存在数组里面了,i下标对应的索引就是多少跟香蕉,警卫离开了,将在h小时后回来。

珂珂决定他吃香蕉的速度,一个小时吃掉k跟,如果这堆香蕉小于k跟,那么就直接吃掉,现在求h小时内吃掉所有香蕉的最小速度

//你可以一个小时内吃掉所有的香蕉,也可以旋转慢慢吃香蕉

确定边界 left是左边界,每个小时必须吃1个

right是右边界,每个小时最多全部吃完

这是第一版写的答案,没有处理好时间关系
int left=1;//不太好确定左边界
        int right= Arrays.stream(piles).max().getAsInt();
        //因为一个小时吃完,那么最快就吃一堆
        //向左进行搜索的
        while(left<right){
            int mid=left+(right-left)/2;
            if (isS(piles,h,mid)){
                //符合的话,是向左进行逼近的
                right=mid; //不断的向左进行收缩 这个速度是可以直接吃完的
            }else {
                left=mid+1;
            }
        }
        return left;
    }
    public  static  boolean isS(int []piles,int h,int speed){
        //如何判断是否满足呢?

        //每一个小时吃一推,多了就不吃了
        int time=1;
        for (int i = 0; i < piles.length; i++) {
            int temp=0;
            //这是这一推的东西
            time++;
            temp=piles[i]-speed;

            while (piles[i]>speed) {
                time++;
                //ear>s,ear-s第一次吃完,ear-s-s第二次吃完,
                //下一次就再减去
                temp=temp-speed;
                if (temp<=0){
                    break;
                }
            }
            //时间边了,就返回false
            if (time>h){
                return false;
            }
        }
        return true;
    }

//正确处理时间关系
class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int right=Arrays.stream(piles).max().getAsInt();
        int left=1;
        while (left<right){
            int mid =left+(right-left)/2;
            int costTime=0;
            for (int pile:piles){
                int currentTime=pile/mid;

                if (mid*currentTime<pile){
                    currentTime++;
                }
              costTime+=currentTime;
            }
         if (costTime<=h){
             //比给定的时间小,要继续向左搜索
             right=mid;
         }else{
             left=mid+1;
             //说明比时间大,速度要加快了
         }
        }
        return left;
    }
}

晚上看完 前缀和 和差分数组 和哈希表

明天早上看 滑动窗口和双指针

前缀和技巧 用于快速、频繁的计算一个索引区间的值之和

返回两个索引之间的值,一般方法
class NumArray {

    private int[] nums;

    public NumArray(int[] nums) {
        this.nums = nums;
    }
    
    public int sumRange(int left, int right) {
        int res = 0;
        for (int i = left; i <= right; i++) {
            res += nums[i];
        }
        return res;
    }
}
//但是这样时间复杂度会特别的高。

//如何进行优化呢?
class NumArray {
    // 前缀和数组
    private int[] preSum;

    /* 输入一个数组,构造前缀和 */
    public NumArray(int[] nums) {
        // preSum[0] = 0,便于计算累加和
        preSum = new int[nums.length + 1];
        // 计算 nums 的累加和
        for (int i = 1; i < preSum.length; i++) {
            preSum[i] = preSum[i - 1] + nums[i - 1];
        }
    }
    
    /* 查询闭区间 [left, right] 的累加和 */
    public int sumRange(int left, int right) {
        return preSum[right + 1] - preSum[left];
    }
}
//这样时间复杂度就是o1了

想要计算nums[i,j]的累加和,只要计算preSum[j+1]-preSum[i]即可;
前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。

差分数组

应用场景是 频繁的原始数组的某个子区间的元素进行增减

比如要将区间nums[2,6]全部加1,再给nums[2,9]全部减3,最后nums是多少

如果用for循环去写的话,时间复杂度很高。

类似于前缀和数组

我们构造一个diff差分数组 diff就是nums[i]-nums[i-1];

差分数组

leetcode 1109

右n个航班,它们分别重1-n进行编号,有一份航班表,表中第i条预定记录bookings[i]=[i,j,k]	意味着从i-j上每个航班预定了k个座位,实质上就是差分数组,对应的数组加1,我们可以构建一个差分数组

哈希表

hash table 也叫做散列表
一般用来快速判断一个元素是否出现在集合里面

数组本身也是一个哈希表

leetcode 242 有效的字母异位词

给定两个字符串 s t,编写一个函数判断t是否是s的字母异位词

采用哈希表
字母对应26个,创建数组,对应的下标索引转换成字母,索引对应的值就是出现的次数

class Solution {
    public boolean isAnagram(String s, String t) {
       int [] record=new int[26]; //字母的长度为26.
        for (int i = 0; i <s.length(); i++) {
            record[s.charAt(i)-'a']++; //对应的下标++;
        }
        for (int i = 0; i <t.length() ; i++) {
            record[t.charAt(i)-'a']--;
        }
        for(int count:record){
            if (count!=0){
                return false;
            }
        }
        return true;
    }
}


leetcode 349 求两个数组之间的交集

//方法一set集合

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
   Integer> set2 =new HashSet<>();
        for (int a:nums1){
            set1.add(a);
        }
        for (int b:nums2){
            if (set1.contains(b)){
                set2.add(b);
            }
        }
        //set2就是两个数组之间重复的元素,在将set转为数组
        int arr [] =new int[set2.size()];
        int j=0;
        for (int arrs:set2){
            arr[j++]=arrs;
        }
        return arr;

    }
    
    //数组
    

常用的hash结构

数组 集合 set 映射(散列表)map;


双指针算法

得到单链表的倒数第k个节点

7.4今天需要学习的内容时

滑动窗口 14届蓝桥杯省赛的真题 百度之星真题 两个小时用来看vue和element。

滑动窗口
就是 维护一个窗口,不断滑动,然后更新答案,时间复杂度是ON,比字符串算法要高效很多。 困难点 如何向窗口中添加新元素,如何缩小窗口
letf=0,right=0;
while()
window.add()
 while(window need shrink){
  
    window.remove();
    left++;
   
}
//滑动窗口
//列题一 一个字符串 s和t,返回s中股改t所有字符的最小字串,如果s中不存在覆盖所有字符额字串,则返回空字符"";
滑动窗口就算结束了
    
螺旋矩阵  54;

晚上需要回看的题目 leetcode209

链表

链表就是右节点组成的
虚拟头节点,可以避免处理空指针的情况,降低代码的复杂度

leetcode21 合并两个链表
leetcode22 和21题刚好相反,是分开一个链表
单链表的倒数第k给节点
//找到链表倒数第k个节点
链表有n个节点,倒数第k个节点就是n-k+1 个节点
但是现在不知道有多少个节点,如果先遍历一遍得到n的值,在遍历链表得到第n-k+1的值,相当于遍历的两次

先让p1走k步,还剩n-k步,再让一个p2指向head,p1和p2同时向前移动,p1走到链表的结尾空指针的时候前进了n-k步
p2也从head开始前进了n-k步,停留在n-k+1的节点上。刚好倒数第k个节点
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
       ListNode dummy=new ListNode(-1);
       dummy.next=head;
       ListNode x =find(dummy,n+1);
       //删除倒数第k个节点,需要找到倒数第k+1个节点,
        // 让倒数k+1节点指向倒数k-1,相当于删除了倒数第k个节点
       x.next=x.next.next; //删除中间的节点

       return dummy.next;


    }

    public static ListNode  find(ListNode head,int k){
        //找到倒数第k个节点
        ListNode p1=head;
        for (int i = 0; i <k ; i++) {
            p1=p1.next;
        }
        ListNode p2=head;
        while (p1!=null){
            p2=p2.next;
            p1=p1.next;
        }
return p2;
    }
}

栈和队列

栈 先进后出的  入栈 和出栈的操作
压栈叫做 push  出栈叫做pop
实列 碗的摆放  羽毛球摆放 
栈是一个特殊的结构,用链表和数组都可以实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnSoXBvY-1691505556709)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230705084110234.png)]

栈常用操作的代码实现

import java.util.Arrays;

public class MyStack{

    private int[] arr;

    // size 记录栈中元素个数
    private int size;

    public MyStack(){
        // 调用无参构造方法 默认最大容量12
        this(12);
    }

    public MyStack(int MaxSize){
        this.arr = new int[MaxSize];
    }

    // 入栈
    public int push(int value){
        if(this.size == arr.length){
            // 栈满 ,需要扩容

            int[] copyArr;
            // 复制arr 数组并扩容一倍
            copyArr = Arrays.copyOf(arr,2 * arr.length);
            arr = copyArr;

        }
        //将元素添加到size位置
        this.arr[size] = value;
        // 元素个数加一
        this.size++;
        // 返回添加元素
        return value;
    }

    // 出栈
    public int pop(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        // 获得栈顶元素
        int value = this.arr[size - 1];
        // size - 1 之后, 下一次插入时会覆盖原数据,利用覆盖替代删除
        this.size--;
        return value;
    }

    // 获取栈顶元素
    public int peek(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        return this.arr[this.size - 1];
    }

    //获取元素个数
    public int getSize(){
        return this.size;
    }

    //判断元素是否为空
    public boolean isEmpty(){
        return this.size == 0;
    }
}

队列 只允许在一端进行操作,在另一端进行删除操作的特殊线性表,队列具有先进先出的特点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XPsrn3HT-1691505556710)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230705084621325.png)]

生活中的列子,排队
linkedlist实现了queue队列的接口,可以通过linkedlist来实例化,
添加元素 offer 删除元素 poll;
栈一般采用链表作为底层结构

基本方法的实现代码

	public class MyLinkedListQueue {
    // 结点
    static class Node {
        public int val;
        public Node next;

        public Node(int val) {
            this.val = val;
        }
    }

    public Node head;
    public Node last;
    public int useSize;

    public MyLinkedListQueue(){
        head = null;
        last = null;
    }

    public void offer(int val) {
        Node tmp = new Node(val);
        if(head == null){
            head = tmp;
            last = tmp;
        }else{
            last.next = tmp;
            last = tmp;
        }
        useSize++;
    }

    public int poll() {
        if(isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        int val = head.val;
        head = head.next;
        if(head == null){
            last = null;
        }
        useSize--;
        return 0;
    }

    public int peek() {
        if(isEmpty()){
            throw new RuntimeException("链表为空");
        }
        return head.val;
    }

    public int size() {
        return useSize;
    }
    public boolean isEmpty(){
        return useSize==0;
 }
 

leetcode 232 用栈来模拟队列

我们知道 栈是先进后出的,队列是先进先出的,可以用两个栈来模拟队列
队列 1 2 3 4 
  栈一[1234
  栈二 1234] 
  //代码
    //使用两个栈来模拟队列的。
  class MyQueue {
   Stack<Integer> stackIn;
   Stack<Integer> stackOut;
    public MyQueue() {
     stackIn= new Stack<>(); //栈1
     stackOut=new Stack<>(); //栈2

    }
    
    public void push(int x) {
    stackIn.push(x);
    }
    
    public int pop() {
        dumpstackIn();
        return  stackOut.pop();
    }
    
    public int peek() {
           dumpstackIn();
           return  stackOut.peek();
    }
    
    public boolean empty() {
  return  stackIn.isEmpty()&&stackOut.isEmpty(); //两个都为空,说明队列才为0
        
    }
    //实现很多类都需要一个函数,直接将这个函数抽象出来。
    private void dumpstackIn(){
        //
        if (!stackOut.isEmpty()) return;  //out里面还有值;
        while (!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
    }
}

用一个队列来模拟栈
队列是先进先出的  leetcode 225
后  先
3 2 1  要来模拟栈,添加元素的时候,在末尾加一个,然后将前面的元素全部弹出来,即这个数就添加到了队首,就模拟了栈,即先进后出,即模拟了队列后进来的先出去。
class MyStack {
    //用队列来模拟栈
    Queue<Integer> queue;
    public MyStack() {
      queue=new LinkedList<>();
    }
    
    public void push(int x) {
       queue.offer(x);
       int size=queue.size();
       while (size-- > 1)
       {
           queue.offer(queue.poll());
       }
    }
    
    public int pop() {return  queue.poll();
    }
    
    public int top() {
        return  queue.peek();
    }
    
    public boolean empty() {
      return queue.isEmpty();
    }
}

leetcode 20题

// 符号匹配,用栈来。
class Solution {
    public boolean isValid(String s) {
        //判断一个括号,用栈来写。
        Stack<Character> stack =new Stack<>();
        if (s.length()%2!=0){
            return false;
        }
        //如果刚开始就是左括号,那么一定不对
        for (int i = 0; i < s.length(); i++) {
            char c=s.charAt(i);

            if (c=='('||c=='{'||c=='['){
                stack.push(c); //将字母放进去
            }else{
                if (c==')'||c==']'||c=='}'){
                    if (stack.size()==0){
                        return false;  //想要出栈,一定要考虑长度是否为0,如果长度为0,直接返回false
                    }
                   char d=stack.peek(); //找出来d;
                   if (d=='('&&c==')'||d=='['&&c==']'||d=='{'&&c=='}'){
                       stack.pop();

                   }else {
                       return false;
                   }
                }
            }
        }
        if (stack.isEmpty()==true){
            //为空
            return true;
        }else {
            return false;
        }
    }

}

栈解决字符串问题

leetcode 1047;

class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack =new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char c=s.charAt(i);
            if (stack.isEmpty() ||stack.peek()!=c){
                stack.push(c); //相邻之间不能相等,不相等进栈
            }else{
                stack.pop(); //如果有相等就出战
            }

        }
        String str="";
        while (!stack.isEmpty()){
            str=stack.pop()+str; //拼接的时候注意顺序
        }
        return str;
    }
}

还可以使用双指针的思想
    class Solution {
    public String removeDuplicates(String s) {
        char[] ch = s.toCharArray(); // 将输入的字符串转换为字符数组
        int fast = 0; // 快指针,用于遍历原始字符数组
        int slow = 0; // 慢指针,用于指示处理后的字符数组的尾部位置
        while (fast < s.length()) {
            ch[slow] = ch[fast]; // 用快指针的值覆盖慢指针的值

            if (slow > 0 && ch[slow] == ch[slow - 1]) { // 如果当前字符与前一个字符相同
                slow--; // 慢指针退后一步,表示将当前重复的字符删除
            } else {
                slow++; // 否则,慢指针前进一步
            }
            fast++; // 快指针前进一步
        }
        
        return new String(ch, 0, slow); // 根据慢指针位置创建新的字符串,表示处理后的结果,即去除重复字符的字符串
    }
}

优先队列

leetocde973

class Solution {
    public int[][] kClosest(int[][] points, int k) {
        PriorityQueue<long[]> queue =new PriorityQueue<>((a,b)->Long.compare(b[0],a[0]));
        //比较的就是距离远点的平方;  //比较规则的时候,需要使用long的包装类
        for (int i = 0; i < points.length; i++) {
            int []cur=points[i]; //当前数组
            //队列里面存放的是数组,数组的零索引是对应的平方和,1索引是对应的下标;
            queue.add(new long[]{1L*cur[0]*cur[0]+cur[1]*cur[1],1l*i}); //将平方和加到队列中去, if(queue.size()>k){
            
        }
            //把对应的距离 和索引都加到数组中去,数组在加到队列中去
        //一共需要k个,找一个出队列的条件
            if (queue.size()>k){
                //寻找k个元素,如果大于了k,就需要讲小的进行返回,规则就是小的在·前面
                queue.poll();
            }

        }
        int [][]ans=new int[k][];

        int p=0;
        while (!queue.isEmpty()) {
            ans[p++] = points[(int)queue.poll()[1]];
        }
        return ans;
    }
}

U307988 数组操作 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

//java代码
import java.util.PriorityQueue;
import java.util.Scanner;

public class Seg implements Comparable<Seg>{


    int len,l,r;

    public Seg(int len, int l, int r) {
        this.len = len;
        this.l = l;
        this.r = r;
    }
     //比较多个内容,长度和左节点,就需要构建一个对象,实现comparable接口,重写compareTo方法
    @Override
    public int compareTo(Seg o) {

        if (len==o.len){
            return l-o.l; //返回一共数,升序排序

        }else {
            //长度不等,就看谁的长度大
            return o.len-len;//进行降序排序
        }

    }
}
class m {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int kase = scanner.nextInt();

        while (kase-- > 0) {
            int n = scanner.nextInt();
            solve(n);
        }
    }
    public static void  solve(int n){

        PriorityQueue<Seg> queue =new PriorityQueue<>();
        queue.add(new Seg(n,1,n));

        int []ans=new int[n+1]; //存储结果

        int cnt=1;

        while (!queue.isEmpty()){
            Seg s=queue.poll();//出来的是长度最大,左端点最小的段
            int mid=s.l+s.r; //取中间值

            if (mid%2==1){
                mid=(mid-1)>>1;
            }else{
                mid>>=1;
            }

            ans[mid]=cnt;//中间的数改变
          cnt++; //还需要再++;

            //都添加到优先队列中去,用规则进行比较,先看长度,长度相等比位置
            //如果长度不等就看长度,先出来的都是靠左,或者是长度比较长的
            if (mid-1>=s.l){
                //说明左边还有0
                queue.add(new Seg(mid-s.l,s.l,mid-1));

            }
            if (s.r >= mid + 1) {
                queue.add(new Seg(s.r - mid, mid + 1, s.r));
            }

        }
        for (int i = 1; i <= n; i++) {
            System.out.print(ans[i] + " ");
        }

        System.out.println();
    }
}

leetcode 347 前k个高频元素

//347
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //一个元素出现的次数,可以想到用hash表来存放这个元素,需要k个,那么就用优先队列,将哈希表里面的元素都添加到优先队列中去,设置比较的规则;
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        //定义了一个hash表·
        for (int num : nums) {
            //讲数组中的元素·全部都·加入·到·哈希表中去
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }

        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] m, int[] n) {
                //比较的是后面出现的次数
                return m[1] - n[1];
            }
        });
        //lamada表达式还可以再进行简化书写
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            //用entrySet来遍历集合,得到对应的键和值
            //将值和键添加到优先队列中去,排序是按照右低到高来排序的
            //当队列里面的数据都满了的时候,将队列里面最小的拿出来进行比较
            //如果小于,如果队列里面小于count,那么就把这个出队,再把新的添加到优先队列中去
            int num = entry.getKey(), count = entry.getValue();
            if (queue.size() == k) {
                if (queue.peek()[1] < count) {
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            } else {
                queue.offer(new int[]{num, count});
            }
        }
        int[] ret = new int[k];
        //需要取k个出来
        for (int i = 0; i < k; ++i) {
            //取出的来是对应map集合的键
            ret[i] = queue.poll()[0];
        }
        return ret;
    }
}

二叉树

需要掌握的内容有

二叉树的遍历方式   dfs(前序 中序 后序)  bfs层序;
二叉树的属性  最小深度  最大深度  平衡二叉树等

二叉树的种类

满二叉树  如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
完全二叉树 除了底层节点可能没有填满,其余每层节点数都到达了最大值,并且最下面一层节点都集中再该层最左边的若干位置;
二叉搜索树 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树
平衡二叉搜索树 AVL树,如果它是一棵空树,或者左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。

存储方式 链式存储就是用的指针 顺序存储方式用的数组 
顺便存储 如果父节点的数组下标是i,那么左孩子就是2*i+1,右孩子是i*2+2;

遍历两种方式

  • 深度优先遍历
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  • 广度优先遍历
    • 层次遍历(迭代法)
二叉树遍历 前序遍历 中序遍历 后序遍历
深度优先搜索 dfs 栈,一般使用递归的方式,前序 中序 后序 都是递归的方式来遍历的
也可以用迭代法来实现,用递归解决都可以用迭代来解决。
    
广度优先搜索 bfs 一层一层的去遍历,再图论中使用的多。队列,迭代法。

前序遍历  中左右 
中序遍历 左中右
后续遍历  左右中

遍历方式,递归方法进行遍历,

// 前序遍历·递归·LC144_二叉树的前序遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        preorder(root, result);
        return result;
    }

    public void preorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        result.add(root.val);
        preorder(root.left, result);
        preorder(root.right, result);
    }
}
// 中序遍历·递归·LC94_二叉树的中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }

    void inorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        inorder(root.left, list);
        list.add(root.val);             // 注意这一句
        inorder(root.right, list);
    }
}
// 后序遍历·递归·LC145_二叉树的后序遍历
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        postorder(root, res);
        return res;
    }

    void postorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        postorder(root.left, list);
        postorder(root.right, list);
        list.add(root.val);             // 注意这一句
    }
}


//迭代法模拟递归,遍历二叉树
//普通方法遍历二叉树
// 采用栈来遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {

        List<Integer> list =new ArrayList<>();
        
        if (root==null){
            
            return  list;
            
        }
        Stack<TreeNode> stack =new Stack<TreeNode>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node =stack.pop();
           list.add(node.val);
            if (node.right!=null){
                stack.push(node.right);
            }
            if (node.left!=null){
                stack.push(node.left);
            }
            //第二次遍历的时候直接弹出来的是左节点,刚刚最后一次添加的左节点
        }
        return list;
    }

}
//可以右前序遍历快速得到后序遍历的代码。
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.left != null){
                stack.push(node.left);
            }
            if (node.right != null){
                stack.push(node.right);
            }
        }
        Collections.reverse(result); //这个找出来的是中右左,犯规来就是左右中。达到的是后序遍历。
        return result;
    }
}
//迭代中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
          if (root==null){
              return  list;
          }
        Stack<TreeNode> stack =new Stack<TreeNode>();
          TreeNode cur=root;
          while (cur!=null||!stack.isEmpty()){
              if (cur!=null){
                  //如何进行遍历
                  //从跟节点开始,这样的效率肯定不如递归遍历
                  stack.push(cur);
                  cur=cur.left;
                  //不断的向左进行遍历
              }else{
                  //遇到null了
                  //弹出栈
                  cur= stack.pop();
                  list.add(cur.val);
                  //左中右的顺序进行遍历
                  cur=cur.right;
              }
          }
          return list;
    }
}
//中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()){
           if (cur != null){
               stack.push(cur);
               cur = cur.left;
           }else{
               cur = stack.pop();
               result.add(cur.val);
               cur = cur.right;
           }
        }
        return result;
    }
}

之前不管是递归或者是迭代遍历用栈,都是dfs,现在层序遍历是bfs;

层序遍历 广度优先搜索,相当于图论里面的广度优先搜索
    //使用的是队列的结构
class Solution {
    // 用bfs实现遍历二叉树,层序遍历二叉树
    public List<List<Integer>> result = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
       checkFun02(root);
   return result;
    }
    public void checkFun02(TreeNode node) {
        if (node == null) return;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(node);
        while (!que.isEmpty()) {
            List<Integer> itemList = new ArrayList<Integer>();
            int len = que.size();
            while (len > 0) {
                TreeNode tmpNode = que.poll();
                itemList.add(tmpNode.val);
                if (tmpNode.left != null) que.offer(tmpNode.left);
                if (tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }
            result.add(itemList);
        }

    }

}

刷题

leetcode 226 翻转二叉树

//bfs
class Solution {
    public TreeNode invertTree(TreeNode root) {
          //先遍历,然后遍历的时候完成交换
   if (root==null) return null;

        //用层序去遍历
        Queue<TreeNode> queue =new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            //将队列中的第一个取出来

            int len=queue.size();

            while (len>0){
                //每次要取出来
                TreeNode poll = queue.poll();
                reverse(poll);
                if (poll.left!=null){
                    queue.offer(poll.left);
                }

                if (poll.right!=null){
                    queue.offer(poll.right);
                }
                len--;
            }
        }
        return root;
    }

    public  static void reverse(TreeNode t){ //实现翻转功能
        {
            TreeNode temp=t.left;
            t.left=t.right;
            t.right=temp;
        }

    }
}

//递归方法
 //前序遍历 和后序遍历都可以
class Solution {
    public TreeNode invertTree(TreeNode root) {
        //得考虑一下
        if (root==null){
            return null;
        }
        //前序遍历  中左右
        invertTree(root.right);
        
        invertTree(root.left);



        swap(root);

        return root;

    }
      public  void swap(TreeNode root){
        TreeNode tmp=root.left;
        root.left=root.right;
        root.right=tmp;
      }

}
 

101对称二叉树

class Solution {
    public boolean isSymmetric(TreeNode root) {
       return compare(root.left,root.right);
        //智能使用后序
        //不断收集左右孩子的信息 返回给上一个节点
        // 左右 中  
    }
    boolean compare(TreeNode left,TreeNode right){
        if (left==null &&right!=null){
            return false;
        }
        if (left!=null &&right==null){
            return false;
        }
        if (left == null && right == null) {
            return true;
        }

        if (left.val != right.val) {
            return false;
        }

        boolean outside=compare(left.left,right.right);
     //后序遍历   
        boolean inside=compare(left.right,right.left);
       //左节点的左孩子 和右节点的右孩子

        return outside&&inside;
    }
}

leetcode 二叉树的最大深度

深度和高度是不同的,高度是到叶子节点的距离,深度是到跟节点

求高度是从下网上遍历,后序遍历 

求深度 可以是前序遍历 中左右,往前遍历一个就+1,往下遍历再加1;

//后序遍历,求得是高度
class solution {
    /**
     * 递归法
     */
    //后序遍历的过程,左右中的遍历顺序
    public int maxDepth(TreeNode root) {
        if (root == null) {           
            return 0;        
        }
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        //左节点和右节点孩子最大的高度 然后+1;
        return Math.max(leftDepth, rightDepth) + 1;
    }
}
//前序遍历
class Solution {
    //类似于回溯法
  /**
   * 递归法(求深度法)
   */
    //定义最大深度
    int maxnum = 0;

    public int maxDepth(TreeNode root) {
        ans(root,0);
        return maxnum;
    }
    
    //递归求解最大深度
    void ans(TreeNode tr,int tmp){
        if(tr==null) return;
        tmp++;
        maxnum = maxnum<tmp?tmp:maxnum;
        ans(tr.left,tmp);
        ans(tr.right,tmp);
        tmp--; //需要进行回溯
    }
}

//思路最清晰的就是迭代法
class solution {
    /**
     * 迭代法,使用层序遍历
     */
    public int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        int depth = 0;
        while (!deque.isEmpty()) {
            int size = deque.size();
            depth++;
            for (int i = 0; i < size; i++) {
                TreeNode node = deque.poll();
                if (node.left != null) {
                    deque.offer(node.left);
                }
                if (node.right != null) {
                    deque.offer(node.right);
                }
            }
        }
        return depth;
    }

二叉树最小深度

//bfs搜索
class Solution {
    int minDepth(TreeNode root) {
        if (root==null){
            return 0;
        }
    //dfs 广度优先搜索
        Queue<TreeNode> queue =new LinkedList<>();
        queue.offer(root); //将根节点添加进来
        int depth=1; //原始的深度
        //用队列来模拟功能
        while (!queue.isEmpty()){
            int size=queue.size();
            for (int i = 0; i <size ; i++) {
                TreeNode cur = queue.poll();
                if (cur.left==null&&cur.right==null){
                    //左右都为null,说明了是根节点了
                    return depth;
                }
                if (cur.left!=null){
                    queue.offer(cur.left);
                }
                if (cur.right!=null){
                    queue.offer(cur.right);
                }
            }
            depth++;
        }
        return depth;
    }
}
//递归方法
//后序求高度
class Solution {
    /**
     * 递归法,相比求MaxDepth要复杂点
     * 因为最小深度是从根节点到最近**叶子节点**的最短路径上的节点数量
     */
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftDepth = minDepth(root.left);
        int rightDepth = minDepth(root.right);
        //如果左边为空,返回右子树的高度+1;
        //后序遍历 ,左右中遍历的方式;
        if (root.left == null) {
            return rightDepth + 1;
        }
        if (root.right == null) {
            return leftDepth + 1;
        }
        // 左右结点都不为null
        return Math.min(leftDepth, rightDepth) + 1;
    }
}

完全二叉树节点的数量

//bfs层序遍历
class Solution {
    // 迭代法
    public int countNodes(TreeNode root) {
        if (root == null) return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int result = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            while (size -- > 0) {
                TreeNode cur = queue.poll();
                result++;
                if (cur.left != null) queue.offer(cur.left);
                if (cur.right != null) queue.offer(cur.right);
            }
        }
        return result;
    }
}
//dfs
class Solution {
    /**
     * 针对完全二叉树的解法
     *
     * 满二叉树的结点数为:2^depth - 1
     */
    public int countNodes(TreeNode root) {
        if (root == null) return 0;
        TreeNode left = root.left;
        TreeNode right = root.right;
        int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
        while (left != null) {  // 求左子树深度
            left = left.left;
            leftDepth++;
        }
        while (right != null) { // 求右子树深度
            right = right.right;
            rightDepth++;
        }
        if (leftDepth == rightDepth) {
            return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
        }
        //如果不是局部满二叉树的化,就相当于是直接进行递归操作;
        
        return countNodes(root.left) + countNodes(root.right) + 1;
    }
}
//正常的递归
class Solution {
    // 通用递归解法
    //当成普通的二叉树去遍历
    public int countNodes(TreeNode root) {
        if(root == null) {
            return 0;
        }
        //后序遍历,左右中;
        return countNodes(root.left) + countNodes(root.right) + 1;
    }
}

leetcode112路径总和

class Solution {
   //用递归解决
    public boolean hasPathSum(TreeNode root, int targetSum) {
        //题目要求从根节点到叶子节点的路径,这条路径上所有节点相加等于目标和targetSum;
      if (root==null){
          return false;
      }
       //找到最后的叶子节点
      if (root.left==null && root.right==null){
          return   targetSum==root.val;
        }
      return hasPathSum(root.left,targetSum-root.val) ||hasPathSum(root.right,targetSum-root.val);
      //和答题那一题很相似
    }

leetcode 124 二叉树中的最大路径和

多天农民画

文化赋能 乡村振兴;

素材组需要剪辑一个视频,加分的一点,

网站的投稿;

图的表示方式 邻接矩阵 和临界表

DFS题目

P1294 高手去散步 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

找一个最长的方案
用邻接矩阵存储图;

从一个点开始寻找,不断的找下去
定义一个二维的矩阵,存储两个点之间距离;

  
        for (int i = 1; i <= n; i++) {
            x = scanner.nextInt();
            y = scanner.nextInt();
            c = scanner.nextInt();
            
            Edge[x][y] = c;
            Edge[y][x] = c;
        }
     
     
   //  dfs代码

import java.util.*;
//所有的变量都是设置的全部变量

class Main {
    static int[][] Edge;
    static int n, s, an, t, ans;
    static boolean[]b;
    static int x, y, c;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        t = scanner.nextInt();
        n = scanner.nextInt();

        Edge = new int[21][21];
        b = new boolean[t+1]; //用布尔数组存放节点

        for (int i = 1; i <= n; i++) {
            x = scanner.nextInt();
            y = scanner.nextInt();
            c = scanner.nextInt();

            Edge[x][y] = c;
            Edge[y][x] = c;
        }

        for (int i = 1; i <= t; i++) {
            //当前路径为ture
            b[i] = true;
            dfs(i, 0);
            an = Math.max(an, ans);
            //找了一次之后,需要访问的结果全部都归为false;
            Arrays.fill(b, false);
        }

        System.out.println(an);
    }

//    static void dfs(int k, int su) {
//        ans = Math.max(ans, su);
//        for (int i = 1; i <= t; i++) {
//
//            if (Edge[k][i] > 0 && b[i]==false) {
//
//                b[i] = true;
//                dfs(i, su + Edge[k][i]);
//                b[i] = false;
//            }
//        }
//    }
    static void dfs(int k,int su){
        ans=Math.max(ans,su);
        for (int i = 0; i < t; i++) {
            if (Edge[k][i]>0&&b[i]==false){
                b[i]=true;
                dfs(i,su+ Edge[k][i]);
                b[i]=false; //撤销操作;
            }
        }
    }
}

并查集

集合关系

自反 对称 传递 ;

并查集的基本操作,初始化init,查询find,合并unionn;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xO4TShjb-1691505556711)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230723155321947.png)]

初始化,用一个数组fa[]来存储每一个元素的父节点

查询 find,寻找i的祖先,

合并操作,将i和j进行一共合并的操作,需要找到id的祖先,在找到j的祖先,将i的祖先指向j的祖先;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NjjZHPzr-1691505556711)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230723160241626.png)]

路径压缩,原来是一很长的链表,变为了图;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KGpuwuOS-1691505556712)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230723160544138.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNuzyE6a-1691505556725)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230723161017940.png)]

//并查集的代码
import java.util.Scanner;

public class BING {
    static int fa[];
    public static void main(String[] args) {
          int n ,m,x,y;

        Scanner scanner =new Scanner(System.in);
       n=scanner.nextInt();
       m=scanner.nextInt();

        fa=new int[n+1];
        init(n);
        for (int i = 1; i <=m; i++) {
           x=scanner.nextInt();
           y=scanner.nextInt();
           union(x,y);
        }
        int q=scanner.nextInt();
        for (int i = 0; i < q; i++) {
            if (find(scanner.nextInt())!=find(scanner.nextInt())){
                System.out.println("NO");
            }else {
                System.out.println("YES");
            }
        }

    }
    //路径压缩的代码
    static void init(int n){
        for (int i = 1; i <=n; i++) {
            fa[i]=i;
        }
    }
    static int find(int i){
        if (fa[i]==i){
            return i;
        }
        else{
            fa[i]=find(fa[i]);//将i的父节点加入进来,进行递归
            return fa[i];
        }
    }
    //通过这一段代码,将所有的节点都指向了一个父节点
   static void union(int i,int j){

        int i_fa=find(i);
        int j_fa=find(j);
         fa[i_fa]=j_fa;
    }
}

牛客刷题总结

数据结构就都先了解一下,不用深挖数据结构得内容,直接过来看算法题,难度可能会有点大,但是必须得坚持下去。

回溯算法

回溯算法是自上而下的
回溯算法可以解决的问题 组合问题 分割问题 子集问题 排列问题 棋盘问题  递增子序列

组合问题 就是 给定两个整数n和k,返回1-n中所有可能的k个数的组合
输入 n=4,k=2; 输出 24 34 23 13 12 14;

抽象为树形的样子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NbqGamGR-1691505556726)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711150012046.png)]

回溯的模板内容

void backtracking(参数 n k startIndex){
if(终止条件)
   存放结果;
   return;
}
  for(选择:本层集合中元素(树中节点孩子的数量就是集合的大小)){
   处理节点;
   backtracking(路径,选择列表); //递归
   回溯,撤销处理的结果;
  }
组合问题的代码
class Solution {
    List<List<Integer>> result = new ArrayList<>(); //数组
    LinkedList<Integer> path = new LinkedList<>(); //链表
    // 定义了两个集合内容
    public List<List<Integer>> combine(int n, int k) {
        backTracking(n, k, 1); //索引位置
        return result;
    }

    public  void backTracking(int n,int k,int startIndex){
        //回溯算法
          if (path.size()==k){
              result.add(new ArrayList<>(path));
              return;
          }

        for (int i = startIndex; i <=n-(k-path.size())+1 ; i++) {
            // k是需要这么多元素,比如需要3个。 path.size是已经添加进去的元素,k-path.size还需要添加的元素
            // 最后+1 这个看做实列来
            //这边进行剪枝优化,可以加大搜索的效率
            path.add(i); //将当前元素加进来

            backTracking(n,k,i+1);

            path.removeLast(); //撤销操作
        }
    }

}

//leetcode submit region end(Prohibit modification and deletion)

组合问题|||

找出所有相加之和为n的k个数的组合,组合中只允许含有1-9的正整数,并且每种组合中不存在重复的数字
k=3 n=9 ; 和组合基本问题相同,引入一个sum变量,在回溯之前加上sum的值,回溯之后减去sum的值即可

结束的条件就是path.size==k,且sum==9;
class Solution {
    public   List<List<Integer>> result =new ArrayList<>();
   public   LinkedList<Integer> path=new LinkedList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
    combine(k,n,1,0);

    return result;

    }
    public   void  combine(int k,int n,int startIndex,int sum){

         if (sum>n){
             return;

         }
       if (path.size()==k){
           if (sum==n) result.add(new ArrayList<>(path));
           return;
       }
        for (int i = startIndex; i <=9-(k-path.size())+1; i++) {
            //有一剪枝优化的操作
            //还需要进行剪枝的操作
            path.add(i);
            sum+=i;
            combine(k,n,i+1,sum);
            //回溯操作 不仅需要将集合里面的元素去除吊,还要将sum减去i;
            path.removeLast();
            //移除最后一个
            sum-=i;

        }


    }
}

电话号码的字母组合

首先需要完成的是映射关系,字母和数字需要完成映射,可以用一个字符数组来解决,字符串操作可以想到的是StringBuilder

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NHkBGaXV-1691505556726)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711152057254.png)]

数是这样的,取出abc中的字母,再去def中的字母,

代码
class Solution {
    List<String> list =new ArrayList<>();
    public List<String> letterCombinations(String digits) {
        //需要解决的问题
        // 数字和字母如何进行映射
        //输入 1 * # 等等异常的情况
        if (digits==null||digits.length()==0){
            return list;
        }
        String [] numString={"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

        backTracking(digits,numString,0);

        return list;

    }
    StringBuilder temp =new StringBuilder();
    //如果这个放在里面的话,每一次回溯都会重新创建一个字符串,不太划算的。
    public void  backTracking(String digits,String []numString,int num){
          //拼接字符串的

        if (num==digits.length()){
            list.add(temp.toString());
            return;
        }

        String str=numString[digits.charAt(num)-'0'];  //找到对应的字母了
     //digits.charAt(num)-‘0’ 得到的是对应数组的下标,str代表数字对应的字符串
        for (int i = 0; i < str.length() ; i++) {
            temp.append(str.charAt(i));
  
            backTracking(digits,numString,num+1);

            temp.deleteCharAt(temp.length()-1); //删除最后一个
        }
    }
}

组合总和问题

1 是 给定一个无重复的元素,candidates中的数字可以无重复的选取使用
组合问题2  是给定一个数组candidates 和一个目标书target,找出candidates中所有可以使数字和为target的组合,数字都只能使用一次。数组里面元素是可以重复的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgVuDLVd-1691505556727)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711152702638.png)]

先取 2 5 3 ,取2的可以继续取253, 取5的只能取53,不能再取2,如果取2了,求出来的结果会重复出现。
代码如下
  需要先进行排序,便于后面进行剪枝操作,如果sum+candidates[i]>target了,说明没有必要再进行递归了,直接结束即可。
    class Solution {
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> path= new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        //Arrays.sort(candidates);
        backTracking(path,candidates,target,0,0);
    return result;
    }
    public void backTracking(List<Integer>path,int []candidates,int target,int sum,int start){

        if (sum>target){
           return;
    }
        if (sum==target){
            result.add(new ArrayList<>(path));
            return;   //结束了
        }
        for (int i = start; i <candidates.length; i++) {
            sum+=candidates[i];
//            if (sum>target){
//                break;//剪枝优化
//            }
            path.add(candidates[i]);
            backTracking(path,candidates,target,sum,i);//
         //组合问题,对应的 start;
            path.remove(path.size()-1);
            sum-=candidates[i];

        }
    }
}

组合总和||的思路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YFzN61PW-1691505556727)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711153055250.png)]

需要进行树层去重,对于里面的元素是不能重复取得,使用过的元素不能重复选取,
组合问题抽象为树形结构,两个维度,一个是树枝上使用过,一个是树层上使用过。
树层上面已经取了1之后,就不能再取1了,不然就会造成重复的现象,这个叫做树层去重,用一个used[]数组来记录结果。

子集问题二 和上面组合问题一个是树枝去重 一个是树层去重

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bChxv79X-1691505556728)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711153855335.png)]

子集问题

子集问题1

子集问题,收获结果,要去每一个节点取收获,之前都是在终点进行收获结果。

nums[123]; 求子集不能出现重复的。给出的nums是不含有重复的;

class Solution {
    //这两个设置的是全局变量
    List<List<Integer>> result =new ArrayList<>();
    LinkedList<Integer> path =new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {

        //如果startindex>=nums.size;
        // 此时已经到了叶子节点,需要进行return;
                     dfs(0,nums);
                     return result;

    }
    public  void dfs(int startIndex,int [] nums){
        result.add (new ArrayList<>(path));

        if (startIndex>=nums.length){
            return;
        }
       //需要startIndex索引,控制位置。
        for (int i = startIndex; i < nums.length; i++)
        {
            path.add(nums[i]);

            dfs(i+1,nums);
            //撤销上一次的操作
            path.removeLast();
        }

    }
}
子集问题2

**输入:**nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] 给出的元素是有重复元素的,

用一个used数组,防止出现重复,used数组还需要进行排序,是为了让相同的元素埃在一起

用startIndex往后进行取元素,需要进行数层的去重,如果元素相同,前面已经用过了,后面就不能再用了;

树层进行去重

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9WrQ54Lz-1691505556728)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230719151544419.png)]

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    List<List<Integer>> result =new ArrayList<>();
    LinkedList<Integer> path =new LinkedList<>();
    boolean [] used;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if (nums.length==0){
            result.add(path);
          return result;
        }
        Arrays.sort(nums);
        used=new boolean[nums.length];
        dfs(nums,0);
        return  result;

    }

    public  void  dfs(int []nums,int startIndex){
        result.add(new ArrayList<>(path)); //将每一个节点上面的元素都添加进来
        //和之前不同的是,需要收获每一个节点的元素
           //结束条件还没有确定呢
        if (startIndex>=nums.length){
            //到结尾了
            return;
        }
        for (int i = startIndex; i < nums.length; i++) {
            //需要进行数层的去重
            if (i>0&&nums[i]==nums[i-1]&&used[i-1]==false){
                                      //刚刚出现了失误的情况
                continue;//直接跳过;
            }
            path.add(nums[i]);
            used[i]=true;
            dfs(nums,i+1);
            path.removeLast();
            used[i]=false;
        }
    }
}

全排列问题

给定一个没有重复的数字序列,返回所有可能的排列
输入: [1,2,3]
输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I9iV2BWY-1691505556729)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711154624917.png)]

全排列问题 2,给定一个可能包含重复数字的序列nums,返回所有不重复的全排列
应该是树层进行去重

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f2rbAk07-1691505556729)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711155156611.png)]

组合问题和排列问题都是在树形结构的叶子节点收集结果,子集问题是取树上所有节点的结果

搜索专题

深度搜索 dfs  广度搜索 bfs 回溯  剪枝 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zyqKPB7M-1691505556729)(C:\Users\周鑫\AppData\Roaming\Typora\typora-user-images\image-20230711164315303.png)]

搜索bfs

bfs 常见的场景,问题的本质是让你在一幅图中找到从start到target的最近距离
要说框架的话,我们先举例一下 BFS 出现的常见场景好吧,问题的本质就是让你在一幅「图」中找到从起点 start 到终点 target 的最近距离,这个例子听起来很枯燥,但是 BFS 算法问题其实都是在干这个事儿,把枯燥的本质搞清楚了,再去欣赏各种问题的包装才能胸有成竹嘛。

这个广义的描述可以有各种变体,比如走迷宫,有的格子是围墙不能走,从起点到终点的最短距离是多少?如果这个迷宫带「传送门」可以瞬间传送呢?

再比如说两个单词,要求你通过某些替换,把其中一个变成另一个,每次只能替换一个字符,最少要替换几次?

再比如说连连看游戏,两个方块消除的条件不仅仅是图案相同,还得保证两个方块之间的最短连线不能多于两个拐点。你玩连连看,点击两个坐标,游戏是如何判断它俩的最短连线有几个拐点的?

再比如……

净整些花里胡哨的,本质上看这些问题都没啥区别,就是一幅「图」,让你从一个起点,走到终点,问最短路径。这就是 BFS 的本质,框架搞清楚了直接默写就好。

bfs的框架

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue<Node> q; // 核心数据结构
    Set<Node> visited; // 避免走回头路
    
    q.offer(start); // 将起点加入队列
    visited.add(start);

    while (q not empty) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj()) {
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
            }
        }
    }
    // 如果走到这里,说明在图中没有找到目标节点
}

岛屿问题

岛屿问题的核心是用bfs/dfs算法遍历二维数组;

岛屿问题的小模版
// 方向数组,分别代表上、下、左、右
int[][] dirs = new int[][]{{-1,0}, {1,0}, {0,-1}, {0,1}};

void dfs(int[][] grid, int i, int j, boolean[][] visited) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        // 超出索引边界
        return;
    }
    if (visited[i][j]) {
        // 已遍历过 (i, j)
        return;
    }

    // 进入节点 (i, j)
    visited[i][j] = true;
    // 递归遍历上下左右的节点
    for (int[] d : dirs) {
        int next_i = i + d[0];
        int next_j = j + d[1];
        dfs(grid, next_i, next_j, visited);
    }
    // 离开节点 (i, j)
}
岛屿的数量
class Solution {
    // 主函数,计算岛屿数量
    int numIslands(char[][] grid) {
        int res = 0;
        int m = grid.length, n = grid[0].length;
        // 遍历 grid
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //如果开始遇到一个岛屿了,开始dfs淹没岛屿
                if (grid[i][j] == '1') {
                    // 每发现一个岛屿,岛屿数量加一
                    res++;
                    // 然后使用 DFS 将岛屿淹了
                    dfs(grid, i, j);
                }
            }
        }
        return res;
    }

    // 从 (i, j) 开始,将与之相邻的陆地都变成海水
    void dfs(char[][] grid, int i, int j) {
        int m = grid.length, n = grid[0].length;
        if (i < 0 || j < 0 || i >= m || j >= n) {
            // 超出索引边界
            return;
        }
        if (grid[i][j] == '0') {
            // 已经是海水了
            return;
        }
        // 将 (i, j) 变成海水
        grid[i][j] = '0';
        // 淹没上下左右的陆地
        //四个方向进行运动;
        dfs(grid, i + 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i - 1, j);
        dfs(grid, i, j - 1);
    }
}
岛屿的最大面积如何计算

类似于求岛屿的数量问题,只不过在每一次遍历的时候需要更新当时的最大面积即可;

单调栈

放入的元素是有序的,要么单调递减,要么单调递增;
递增:从栈底到栈顶的数据是从小到大的
递减:从栈底到栈顶的数据是从大到小的;

单调栈的伪代码
stack<int> st;
//此处一般需要给数组最后添加结束标志符,具体下面例题会有详细讲解
for (遍历这个数组)
{
	if (栈空 || 栈顶元素大于等于当前比较元素)
	{
		入栈;
	}
	else
	{
		while (栈不为空 && 栈顶元素小于当前元素)
		{
			栈顶元素出栈;
			更新结果;
		}
		当前数据入栈;
	}
}

数学逻辑问题

leetcode 292
leetcode 319
leetcode 877

877
class Solution {
    public int bulbSwitch(int n) {

        return  (int)Math.sqrt(n);
        //刚开始都是关闭的,如果某一栈灯是最后点亮的,那么他一定要被按奇数次开关

        //比如有六栈灯,需要进行六次操作,
        // 对于第六个灯会被按多少次呢?
        // 1 2 3 6都会被按
        //  求16的平方跟,因为最后会有4栈灯亮着
        // 第1 4 9 16;
        
        //一般情况下一个数的因子都是成对出现的
        //比如3=1*3
        //只有n的平方是只出现了一次的  4=2*2,那么这样就相当于操作了奇数次
        //最后就一定会亮着了,需要对一个数求它的平方根;
    }
}

贪心算法

局部最优推导全局最优

简单题目

455 分发饼干

技巧类算法

位运算

//打印十进制的二进制位数
public class 位运算 {
    public static void main(String[] args) {

        Scanner sc= new Scanner(System.in);
        solve(sc.nextInt());

    }
    public  static  void solve(int num){
        //1->0000 0000 0000 0000 0000 0000 0000 0001
        for (int i = 31; i >=0 ; i--) {
            //将十进制转为二进制;
            System.out.print((num&(1<

给字符串排序

你可能感兴趣的:(算法,开发语言,java)