LeetCode刷题笔记

LeetCode

常见的容器

List

Java List 列表定义了一系列的操作方法,这里总结如下:

img

LinkedList 除了继承了 List 的操作方法之外,还定义一些特殊的方法:

img

① add(Object e):向集合末尾处,添加指定的元素!

② add(int index, Object e):向集合指定索引处,添加指定的元素,原有元素依次后移!

③ remove(Object e):将指定元素对象,从集合中删除,返回值为被删除的元素!

④ remove(int index):将指定索引处的元素,从集合中删除,返回值为被删除的元素!

⑤ set(int index, Object e):将指定索引处的元素,替换成指定的元素,返回值为替换前的元素(Set前提必须有这个下标)!

⑥ get(int index):获取指定索引处的元素,并返回该元素!

HashMap

  • HashMap是基于哈希表的 Map 接口的实现,是无序的
API解释
clear()清空。
containsKey(Object key)如果包含指定键,返回true
containsValue(Object value)如果包含指定值, 返回true
get(Object key)返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null
isEmpty()如果此映射不包含键-值映射关系,则返回 true
put(K key, V value)在此映射中关联指定值与指定键
remove(Object key)从此映射中移除指定键的映射关系(如果存在)
size()返回此映射中的键-值映射关系数

Map.Entry

Map是java中的接口,Map.Entry是Map的一个内部接口。

Map提供了一些常用方法,如keySet()、entrySet()等方法,keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。

Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。

使用1:for-each中使用的场景(性格能高)

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
 
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
 
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
 
}

使用2:for-each循环中遍历keys或values(性能高)

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
 
//遍历map中的键
 
for (Integer key : map.keySet()) {
 
    System.out.println("Key = " + key);
 
}
 
//遍历map中的值
 
for (Integer value : map.values()) {
 
    System.out.println("Value = " + value);
 
}

使用3:使用Iterator遍历

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();

while (entries.hasNext()) {
  Map.Entry<Integer, Integer> entry = entries.next();

  System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
  }

使用4:通过键找值遍历(性能最差)

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Integer key : map.keySet()) {
  Integer value = map.get(key);

  System.out.println("Key = " + key + ", Value = " + value);
}

HashSet

  • HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
  • HashSet 允许有 null 值。
  • HashSet 是无序的,即不会记录插入的顺序。
  • HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。
  • HashSet 实现了 Set 接口。

常见API

add()增加元素 成功添加返回truesetname.add(new_object);
contains()判断是否存在一个元素 返回true或者falsesetname.contains(specific_object);
remove()删除一个制定元素 若存在,并删除成功则返回truesetname.remove(useless_object);
isEmpty()判断集合是否为空,为空则为truesetname.isEmpty();
clear()清空集合 但没有删除对象setname.clear();
size()计算集合的大小setname.size();

Queue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OS2TjrnA-1667540758211)(https://gitee.com/stars_shine/cloud-image/raw/master/image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI3MTg0NDk3,size_16,color_FFFFFF,t_70.png)]

方法名称作用描述
add添加元素到队列如果队列满了就抛异常java.lang.IllegalStateException
remove移除并且返回队列头部元素如果队列为null,就抛异常java.util.NoSuchElementException
element返回队列头部元素,不会移除元素如果队列为null,就抛异常java.util.NoSuchElementException
offer添加元素到队列如果队列满了就返回false,不会阻塞
poll移除并且返回队列头部元素如果队列为null,就返回null,不会阻塞
peek返回队列头部元素,不会移除元素如果队列为null,就返回null,不会阻塞
put添加元素到队列如果队列满了就阻塞
take移除并且返回队列头部元素如果队列为null,就阻塞
  • 类结构

img

Queue使用时要尽量避免Collectionadd()remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常如果要使用前端而不移出该元素,使用element()或者peek()方法。

Queue 实现通常不允许插入 null 元素,尽管某些实现(如 LinkedList)并不禁止插入 null。即使在允许 null 的实现中,也不应该将 null 插入到 Queue 中,因为 null 也用作 poll 方法的一个特殊返回值,表明队列不包含元素。

值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

 private void testDeque() {

        Deque<String> tmpDeque = new LinkedList<>();
        tmpDeque.offer("Hello");
        tmpDeque.offer("World");
        tmpDeque.offer("你好!");
        Log.d(TAG, "tmpDeque.size():" + tmpDeque.size());
        String str = null;
        while ((str = tmpDeque.poll()) != null) {
            Log.d(TAG, "str : " + str);
        }
        Log.d(TAG, "tmpDeque.size():" + tmpDeque.size());
    }

Deque

一个线性 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。

功能:

  • 双端数组,可以对头端进行插入删除操作
一、Queue和Deque

1,Deque是Queue的子接口;

从源码中可以得知:Queue以及Deque都是继承于Collection,Deque是Queue的子接口。

public interface Deque<E> extends Queue<E> {}

2,Queue——单端队列Deque——双端队列;

从Deque的解释中,我们可以得知:Deque是double ended queue,我将其理解成双端队列,就是可以在首和尾都进行插入或删除元素。

而Queue的解释中,Queue就是简单的FIFO(先进先出)队列。所以在概念上来说,Queue是FIFO的单端队列,Deque是双端队列。

**3,**Queue常用子类——PriorityQueue;Deque常用子类——LinkedList以及ArrayDeque;

Queue有一个直接子类PriorityQueue。

而Deque中直接子类有两个:LinkedList以及ArrayDeque。

二、PriorityQueue:

img

PriorityQueue的底层数据结构是数组,而无边界的形容,那么指明了PriorityQueue是自带扩容机制的

三、LinkedList以及ArrayDeque:

img

从官方解释来看,ArrayDeque是无初始容量的双端队列,LinkedList则是双向链表。

而我们还能看到,ArrayDeque作为队列时的效率比LinkedList要高。

而在栈的使用场景下,无疑具有尾结点,不需判空的LinkedList更为高效。

演示ArrayDeque作为队列以及LinkedList作为栈的使用:

private static void usingAsQueue() {
        Deque<Integer> queue=new ArrayDeque<>();
 
        System.out.println("队列为空:"+queue.isEmpty());   //判断队列是否为空
 
        queue.addLast(12);   //添加元素
        System.out.println(queue.peekFirst());   //获取队列首部元素
        System.out.println(queue.pollFirst());   //获取并移除栈顶元素
 
 }
 
private static void usingAsStack() {
        //作为栈使用
        Deque<Integer> stack=new LinkedList<>();
 
        System.out.println("栈为空:"+stack.isEmpty());   //判断栈是否为空
 
        stack.addFirst(12);//添加元素
        System.out.println(stack.peekFirst());   //获取栈顶元素
        System.out.println(stack.pollFirst());   //获取并移除栈顶元素
    }

小提示:

在Deque中,获取并移除元素的方法有两个,分别是removeXxx以及peekXxx。

存在元素时,两者的处理都是一样的。

但是当Deque内为空时,removeXxx会直接抛出NoSuchElementException,

而peekXxx则会返回null。

所以无论在实际开发或者算法时,推荐使用peekXxx方法

其实ArrayDeque和LinkedList都可以作为栈以及队列使用,但是从执行效率来说,ArrayDeque作为队列,以及LinkedList作为栈使用,会是更好的选择。

四、小结:
  • PriorityQueue可以作为堆使用,而且可以根据传入的Comparator实现大小的调整,会是一个很好的选择。
  • ArrayDeque可以作为栈或队列使用,但是栈的效率不如LinkedList高,通常作为队列使用
  • LinkedList可以作为栈或队列使用,但是队列的效率不如ArrayQueue高,通常作为栈使用
五、Queue和Deque的方法区别:

在java中,Queue被定义成单端队列使用Deque被定义成双端队列使用

而由于双端队列的定义,Deque可以作为栈或者队列使用

Queue只能作为队列或者依赖于子类的实现作为堆使用。

方法上的区别如下:

QueueDeque
addaddFirst
offerofferFirst
removeremoveFirst
pollpollFirst
elementgetFirst
peekpeekFirst
  • ​ 将Deque 用作单纯的队列(FIFO,尾进头出,先进先出)

在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:

Queue的方法等效Deque的方法描述
add(E e)addLast(E e)添加至tail,失败,则抛异常
offer()offerLast()添加至tail,失败,则返回false
remove()removeFirst()移除head,失败,则抛异常
poll()pollFirst()移除head,失败,返回null
element()getFirst()检查head,不移除,失败,则抛异常
peek()peekFirst()检查head,不移除,失败,则返回null

img

img

  • 将Deque 用作栈(LIFO,头进头出,后进先出,比如activity的栈)

在将双端队列用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:

堆栈方法等效Deque的方法描述
push(e)addFirst(e)添加至头部,失败,则抛异常
offerFirst(e)添加至头部,失败,则返回false
pop()removeFirst()移除头部,失败,则抛异
pollFirst()移除头部,失败,则返回false
peek()getFirst()检查头部,失败,则抛异常
peekFirst()检查头部,失败,则返回null

img

deque与vector区别:
  • vector对于头部的插入删除效率低,数据量越大效率越低。
  • deque相对而言,对头部的插入删除速度会比vector块。
  • vector访问元素时的速度会比deque快,这和两者的内部实现有关。

ArrayDeque

  • ArrayDeque是 Deque接口的一个实现,使用了可变数组,所以没有容量上的限制。同时, ArrayDeque是线程不安全的,在没有外部同步的情况下,不能再多线程环境下使用。
  • ArrayDeque是 Deque的实现类,可以作为栈来使用,效率高于 Stack;也可以作为队列来使用,效率高于 LinkedList。
  • ArrayDeque 是 Java 集合中双端队列的数组实现,双端队列的链表实现(LinkedList)
  • 需要注意的是, ArrayDeque不支持 null值。
 1.添加元素
        addFirst(E e)在数组前面添加元素
        addLast(E e)在数组后面添加元素
        offerFirst(E e) 在数组前面添加元素,并返回是否添加成功
        offerLast(E e) 在数组后天添加元素,并返回是否添加成功
 
  2.删除元素
        removeFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将抛出异常
        pollFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将返回null
        removeLast()删除最后一个元素,并返回删除元素的值,如果为null,将抛出异常
        pollLast()删除最后一个元素,并返回删除元素的值,如果为null,将返回null
        removeFirstOccurrence(Object o) 删除第一次出现的指定元素
        removeLastOccurrence(Object o) 删除最后一次出现的指定元素
   
 
   3.获取元素
        getFirst() 获取第一个元素,如果没有将抛出异常
        getLast() 获取最后一个元素,如果没有将抛出异常
   
 
    4.队列操作
        add(E e) 在队列尾部添加一个元素
        offer(E e) 在队列尾部添加一个元素,并返回是否成功
        remove() 删除队列中第一个元素,并返回该元素的值,如果元素为null,将抛出异常(其实底层调用的是removeFirst())
        poll()  删除队列中第一个元素,并返回该元素的值,如果元素为null,将返回null(其实调用的是pollFirst())
        element() 获取第一个元素,如果没有将抛出异常
        peek() 获取第一个元素,如果返回null
      
 
    5.栈操作
        push(E e) 栈顶添加一个元素
        pop(E e) 移除栈顶元素,如果栈顶没有元素将抛出异常
        
 
    6.其他
        size() 获取队列中元素个数
        isEmpty() 判断队列是否为空
        iterator() 迭代器,从前向后迭代
        descendingIterator() 迭代器,从后向前迭代
        contain(Object o) 判断队列中是否存在该元素
        toArray() 转成数组
        clear() 清空队列
        clone() 克隆(复制)一个新的队列

image-20220910153648943

1.动态规划

198. 打家劫舍

状态方程:

dp[i] = max(dp[i-2]+nums[i], dp[i-1])

边界条件:

  • n=0,dp=0
  • n=1,dp = nuns(0);
class Solution {
    public int rob(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }
    int N = nums.length;
    int[] dp = new int[N+1];
    dp[0] = 0;
    dp[1] = nums[0];
    for (int k = 2; k <= N; k++) {
        dp[k] = Math.max(dp[k-1], nums[k-1] + dp[k-2]);
    }
    return dp[N];
    }
}
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。


class Solution {
    public int rob(int[] nums) {
        int pre = 0, cur = 0, tmp;
        for(int num : nums) {
            tmp = cur;
            cur = Math.max(pre + num, cur);
            pre = tmp;
        }
        return cur;
    }
}

213. 打家劫舍 II

1658714065346

1658714047731

思路:

因为房间成环,所以要保证第一间和最后一间不能同时被偷窃

  • 偷窃了第一间,最后一间不偷,范围为[0,n-2]
  • 偷窃最后一间,第一间不偷,范围为[1,n-1]

状态方程为:

dp[i]=max(dp[i−2]+nums[i],dp[i−1])

边界条件:

image-20220912190838086

优化空间复杂度:

dp[n] 只与 dp[n−1] 和 dp[n−2] 有关系,因此我们可以设两个变量 cur和 pre 交替记录,将空间复杂度降到 O(1) 。

class Solution {
    public int rob(int[] nums) {
        if(nums.length == 0) return 0;
        if(nums.length==1) return nums[0];
        return Math.max(myRob(Arrays.copyOfRange(nums,0,nums.length-1)),
                        myRob(Arrays.copyOfRange(nums,1,nums.length)));
    }
    private int myRob(int[] nums){
        int pre = 0, cur = 0,temp;
        for(int num : nums){
            temp = cur ;
            cur = Math.max(pre+ num,cur);
            pre = temp;
        }
        return cur;
    }
}

数组截取方法-Arrays.copyOfRange():将一个原始的数组original,从下标from开始复制,复制到上标to,生成一个新的数组返回。

注意:这里包括下标from,不包括上标to。[from,to)

337. 打家劫舍 III

image-20220912192613008

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
    int[] result = robInternal(root);
    return Math.max(result[0], result[1]);
    }

    public int[] robInternal(TreeNode root) {
    if (root == null) return new int[2];
    int[] result = new int[2];

    int[] left = robInternal(root.left);
    int[] right = robInternal(root.right);

    result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
    result[1] = left[0] + right[0] + root.val;

    return result;
    }

}

53.最大子数组和

1658932074742

class Solution {
    public int maxSubArray(int[] nums) {
        int pre = 0;
        int res= nums[0];
        for(int num: nums){
            pre = Math.max(pre + num , num);
            res = Math.max(pre,res);
        }
        return res;
    }
}

1143. 最长公共子序列

1659059242066

动态规划
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m+1][n+1];

        for(int i =1;i<=m;i++){
            for(int j = 1;j<=n;j++){
                if(text1.charAt(i-1) == text2.charAt(j-1)){
                    dp[i][j] = 1+dp[i-1][j-1];
                } else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
}
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m+1][n+1];
        for(int i = 1;i<=m;i++){
            char c1 = text1.charAt(i-1);
            for(int j = 1;j<=n;j++){
                char c2 =  text2.charAt(j-1);
                if(c1 ==c2){
                    dp[i][j]= dp[i-1][j-1]+1;
                }else{
                    dp[i][j]= Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
}

72. 编辑距离

1659063552318

1659063527918

class Solution {
    public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();
        int[][] dp = new int[n1 + 1][n2 + 1];
        // 第一行
        for (int j = 1; j <= n2; j++) dp[0][j] = dp[0][j - 1] + 1;
        // 第一列
        for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;

        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
            }
        }
        return dp[n1][n2];  
    }
}


300. 最长递增子序列

1659064684884

// Dynamic programming.
class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums.length == 0) return 0;
        int[] dp = new int[nums.length];
        int res = 0;
        Arrays.fill(dp, 1);
        for(int i = 0; i < nums.length; i++) {
            for(int j = 0; j < i; j++) {
                if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

动态规划+二分查找

1659066693226

// Dynamic programming + Dichotomy.
class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] tails = new int[nums.length];
        int res = 0;
        for(int num : nums) {
            int i = 0, j = res;
            while(i < j) {
                int m = (i + j) / 2;
                if(tails[m] < num) i = m + 1;
                else j = m;
            }
            tails[i] = num;
            if(res == j) res++;
        }
        return res;
    }
}

55. 跳跃游戏

1659070259116

class Solution {
    public boolean canJump(int[] nums) {
        int len = nums.length;
        int farthest = 0;
        for(int i =0;i<len-1;i++ ){
            farthest = Math.max(farthest,i+nums[i]);
            if(farthest<=i){
                return false;
            }
        }
         return farthest >= len-1;
    }
}

逆序遍历数组,num用来存储能达到最后一个下标的最小值,最后判断数组nums下标为0的数是否能跳跃至num,能跳跃至num即为true,否则false

class Solution {
    //逆序遍历数组
    public boolean canJump(int[] nums) {
        int j ,num=nums.length-1;
        for(int i = nums.length-2;i>0;i--){
            j = nums[i];
            if(i+j>= num){
                num = Math.min(i,num);
            }
        }
        if(0+nums[0]>= num){
            return true;;
        }
        return false;
    }
}
class Solution {
    public boolean canJump(int[] nums) {
        // 记录能跳到的【最远距离】 max
        int max = 0;
        for (int i = 0; i < nums.length ; i++) { // 最终 i == nums.length - 1
            // 如果i > max,说明这个位置跳不到,return false,可能中间就return false了
            if (i > max) {
                return false;
            }
            max = Math.max(max, i + nums[i]); // 更新最远距离
        }
        return true;
    }
}
//动态规划
class Solution {
    public boolean canJump(int[] nums) {
        if (nums.length == 1) {
            return true;
        }
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        for(int i = 1;i < nums.length - 1;i++){
            if (dp[i-1] < i) return false;
            dp[i] = Math.max(dp[i-1],nums[i] + i);
        }
        return dp[nums.length - 2] >= nums.length - 1;
    }
}

62. 不同路径

1659072197471

1659072547711

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        for(int i=0;i<m;++i){
            dp[i][0]=1;
        }
        for(int j= 0;j<n;++j){
            dp[0][j]=1;
        }
        for(int i=1;i<m;++i){
            for(int j =1;j<n;++j){
                dp[i][j] = dp[i-1][j] +dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
}
class Solution {
    public int uniquePaths(int m, int n) {
        int cur = new int[n];
        Arrays.fill(cur,1);
        for(int j = 1;j<n;j++){
            cur[j] += cur[j-1];
        }
        return cur[n-1];
    }
}

1659072797196

class Solution {
    public int uniquePaths(int m, int n) {
        long ans = 1;
        for(int x= n, y=1;y<m;++x,++y){
            ans = ans*x/y;
        }
        return (int)ans;
    }
}

64. 最小路径和

1659073264374

1659074969396

class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dp = new int[m][n];
        dp[0][0] = grid[0][0];
        for(int i =1; i<m;i++){
            dp[i][0]=dp[i-1][0]+grid[i][0];
        }
        for(int j =1; j<n;j++){
            dp[0][j]=dp[0][j-1]+grid[0][j];
        }
        for(int i =1; i<m;i++){
            for(int j =1; j<n;j++){
                dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }
        return dp[m-1][n-1];

    }
}

647. 回文子串

image-20220730210216845

class Solution {
    public int countSubstrings(String s) {
        boolean[][] dp = new boolean[s.length()][s.length()];
        int ans = 0;
        for(int j = 0;j<s.length();j++){
            for(int i = 0;i<=j;i++){
                if(s.charAt(i) == s.charAt(j) && (j-i<2 || dp[i+1][j-1])){
                    dp[i][j]=true;
                    ans++;
                }
            }
        }
        return ans;

    }
}

中心扩展法

class Solution {
    public int countSubstrings(String s) {
        int ans = 0;
        for(int center = 0;center < 2*s.length()-1;center++){
            int left= center/2;
            int right=left + center % 2;
            while(left >= 0 && right<s.length() && s.charAt(left) == s.charAt(right)){
                ans++;
                left--;
                right++;
            }
        }
        return ans;
    }
}

70. 爬楼梯

image-20220730212148877

//自己做的
class Solution {
    public int climbStairs(int n) {
        if(n<=2) return n;
        int[] dp = new int[n];
        dp[0] = 1;
        dp[1] = 2;
        for(int i = 2; i <n ;i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n-1];
    }
}

class Solution {
    public int climbStairs(int n) {
        double sqrt5 = Math.sqrt(5);
        double fibn =Math.pow((1+sqrt5)/2,n+1) - Math.pow((1-sqrt5)/2,n+1);
        return (int) Math.round(fibn/sqrt5);
    }
}

121. 买卖股票的最佳时机

image-20220730215858505

//动态规划 自己做的
class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        if(len == 1) return 0;
        int res = prices[0];
        int[] dp = new int[len];
        dp[0] = 0;
        for(int i = 1; i<=len-1;i++){
            if(prices[i] > res){
                dp[i] = Math.max(prices[i] -res,dp[i-1]);
            }else{
                res = prices[i];
                dp[i] = dp[i-1];
            }
        }
        return dp[len-1];

    }
}
class Solution {
    public int maxProfit(int[] prices) {
            int length = prices.length;
            //两个状态:手里没股票,手里有股票
            int dp0 = 0,dp1 = Integer.MIN_VALUE;
            for(int i = 0;i < length; i ++){
                //手里没股票
                dp0 = Math.max(dp0,dp1 + prices[i]);
                //手里有股票
                dp1 = Math.max(dp1,-prices[i]);
            }
            //返回没股票的时候
            return dp0;

    }
}

class Solution {
    public int maxProfit(int[] prices) {
        int cur = 0,pre = Integer.MIN_VALUE;
        for(int i = 0;i<prices.length;i++){
            cur =Math.max(cur,pre+prices[i]);
            pre=Math.max(pre,-prices[i]);
        }
        return cur;
    }
}

贪心算法

class Solution {
    public int maxProfit(int[] prices) {
        int cost = Integer.MAX_VALUE, profit = 0;
        for (int price : prices) {
            cost = Math.min(cost, price);
            profit = Math.max(profit, price - cost);
        }
        return profit;
    }
}

139. 单词拆分

image-20220731102559107

Collectors.toSet() 将流中的所有元素导出到一个列表( Set)

Collectors.toList() 将流中的所有元素导出到一个列表( List )

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
            int n = s.length();
            Set<String> dict = wordDict.stream().collect(Collectors.toSet());
            boolean[] dp = new boolean[n+1];
            dp[0] = true;
            for(int i =1;i<=n;i++){
                for(int j =0;j<i;j++){
                    if(dp[j] && dict.contains(s.substring(j,i))){
                        dp[i] = true;
                        break;
                    }
                }
            }
            return dp[n];

    }
}
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        int len = s.length();
        Set<String> dict = new HashSet<>();
        for(String word :wordDict){
            dict.add(word);
        }
        //  设置长度为s.length() + 1的一维数组,设置哨兵位dp[0] = true
        //  dp[i]表示[1,i]的字符串能否在wordDict中找到

        boolean[] dp = new boolean[len +1];
        dp[0] = true;
        //  设置j指针从头扫到当前切点
            //  j指针每次后移都要确保以j为切点的前缀满足
        for(int i = 1;i<=len;i++){
            for(int j = 0;j<i;j++){
                if(dp[j] && dict.contains(s.substring(j,i))){
                    //  找到切点就退出
                    //  只要当前切点字符串满足就跳出j循环
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[len];

    }
}

1696. 跳跃游戏 VI

image-20220817134045086

class Solution {
    public int maxResult(int[] nums, int k) {
        int n = nums.length;
        if(n == 1) return nums[0];
        int[] dp = new int[n];
        Arrays.fill(dp,Integer.MIN_VALUE);
        dp[0] = nums[0];
        for(int i = 0;i < n;i++){
            for(int j = i+1; j<= i+k && j<n;j++){
                if(dp[j] < dp[i] + nums[j]){
                    dp[j] = dp[i] + nums[j];
                }
                if(dp[j] >= dp[i]){
                     break;
                }
            }
           
        }
        return dp[n-1];
    }
}

魔塔闯关

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dLy1YiV3-1667540758225)(https://gitee.com/stars_shine/cloud-image/raw/master/image/image-20220909232848234.png)]

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] martic = new int[n][2];
        int count = 0;
        long res = 0;
        for (int i = 0; i < n; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            if (b == 0) {
                res +=a;
            }else {
                count++;
            }
            martic[i][0] = a;
            martic[i][1] = b;
        }
        int[] dp = new int[count];
        int index = 0;
        for (int i = 0; i < n; i++) {
            if (martic[i][1] == 1){
                dp[index++] =martic[i][0];
            }
        }
        Arrays.sort(dp);
        for (int i = dp.length - 1; i >= 0; i--) {
            if (res < dp[i]) {
                res += dp[i];
            }else {
                res *= 2;
            }
        }
        System.out.println(res);

    }

}

2.DFS/BFS

image-20220731113558980

22. 括号生成

image-20220731110601354

DFS:

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        if(n==0) return res;
        dfs("",0,0,n,res);
        return res;
    }

    private void dfs(String curStr,int left,int right,int n,List<String> res){
        if(left == n && right == n){
            res.add(curStr);
            return;
        }
        //剪枝
        if(left<right){
            return;
        }
        if(left<n){
            dfs(curStr + "(",left+1,right,n,res);
        }
        if(right<n){
            dfs(curStr + ")",left,right+1,n,res);
        }
    }
}
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        if (n == 0) {
            return res;
        }

        StringBuilder path = new StringBuilder();
        dfs(path, n, n, res);
        return res;
    }


    /**
     * @param path  从根结点到任意结点的路径,全程只使用一份
     * @param left  左括号还有几个可以使用
     * @param right 右括号还有几个可以使用
     * @param res
     */
    private void dfs(StringBuilder path, int left, int right, List<String> res) {
        if (left == 0 && right == 0) {
            // path.toString() 生成了一个新的字符串,相当于做了一次拷贝,这里的做法等同于「力扣」第 46 题、第 39 题
            res.add(path.toString());
            return;
        }

        // 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
        if (left > right) {
            return;
        }

        if (left > 0) {
            path.append("(");
            dfs(path, left - 1, right, res);
            path.deleteCharAt(path.length() - 1);
        }

        if (right > 0) {
            path.append(")");
            dfs(path, left, right - 1, res);
            path.deleteCharAt(path.length() - 1);
        }
    }
}

101. 对称二叉树

image-20220731114738978

递归:

image-20220801102118000



class Solution {
    public boolean isSymmetric(TreeNode root) {
        return check(root,root);
    }
    public boolean check(TreeNode p, TreeNode q){
        if(p == null && q == null) return true;
        if(p == null || q == null) return false;
        return p.val == q.val && check(p.left,q.right) && check(p.right,q.left);

    }
}


迭代:

class Solution {
	public boolean isSymmetric(TreeNode root) {
		if(root==null || (root.left==null && root.right==null)) {
			return true;
		}
		//用队列保存节点
		LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
		//将根节点的左右孩子放到队列中
		queue.add(root.left);
		queue.add(root.right);
		while(queue.size()>0) {
			//从队列中取出两个节点,再比较这两个节点
			TreeNode left = queue.removeFirst();
			TreeNode right = queue.removeFirst();
			//如果两个节点都为空就继续循环,两者有一个为空就返回false
			if(left==null && right==null) {
				continue;
			}
			if(left==null || right==null) {
				return false;
			}
			if(left.val!=right.val) {
				return false;
			}
			//将左节点的左孩子, 右节点的右孩子放入队列
			queue.add(left.left);
			queue.add(right.right);
			//将左节点的右孩子,右节点的左孩子放入队列
			queue.add(left.right);
			queue.add(right.left);
		}
		
		return true;
	}
}

102. 二叉树的层序遍历

DFS 遍历使用递归:

void dfs(TreeNode root) {
    if (root == null) {
        return;
    }
    dfs(root.left);
    dfs(root.right);
}

BFS 遍历使用队列数据结构:

void bfs(TreeNode root) {
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll(); // Java 的 pop 写作 poll()
        if (node.left != null) {
            queue.add(node.left);
        }
        if (node.right != null) {
            queue.add(node.right);
        }
    }
}

只是比较两段代码的话,最直观的感受就是:DFS 遍历的代码比 BFS 简洁太多了!这是因为递归的方式隐含地使用了系统的 栈,我们不需要自己维护一个数据结构。如果只是简单地将二叉树遍历一遍,那么 DFS 显然是更方便的选择。

BFS 遍历的过程中,结点进队列和出队列的过程:

BFS 遍历的过程(动图)

// 二叉树的层序遍历
void bfs(TreeNode root) {
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        int n = queue.size();
        for (int i = 0; i < n; i++) { 
            // 变量 i 无实际意义,只是为了循环 n 次
            TreeNode node = queue.poll();
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
    }
}


BFS 遍历改造成了层序遍历。在遍历的过程中,结点进队列和出队列的过程为:

img

在 while 循环的每一轮中,都是将当前层的所有结点出队列,再将下一层的所有结点入队列,这样就实现了层序遍历。

层序遍历一个二叉树:就是从左到右一层一层的去遍历二叉树。

image-20220808111018495

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        Queue<TreeNode> queue = new ArrayDeque<>();
        if(root != null){
            queue.add(root);
        }
        while(!queue.isEmpty()){
            int n = queue.size();
            List<Integer> level = new ArrayList<>();
            for(int i = 0;i<n;i++){
                TreeNode node = queue.poll();
                level.add(node.val);
                if(node.left != null){
                    queue.add(node.left);
                }
                if(node.right != null){
                    queue.add(node.right);
                }
            }
            res.add(level);
        }
        return res;
    }
}

987. 二叉树的垂序遍历

image-20220808185902058

DFS + 哈希表 + 排序

根据题意,我们需要按照优先级「“列号从小到大”,对于同列节点,“行号从小到大”,对于同列同行元素,“节点值从小到大”」进行答案构造。

因此我们可以对树进行遍历,遍历过程中记下这些信息 (col, row, val)(col,row,val),然后根据规则进行排序,并构造答案。

我们可以先使用「哈希表」进行存储,最后再进行一次性的排序。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    Map<TreeNode ,int[] > map = new HashMap<>();
    public List<List<Integer>> verticalTraversal(TreeNode root) {
        map.put(root,new int[]{0,0,root.val});
        dfs(root);
        List<int[]> list = new ArrayList<>(map.values());
        Collections.sort(list,(a,b)->{
            if(a[0] != b[0]) return a[0] - b[0];
            if(a[1] != b[1]) return a[1] - b[1];
            return a[2] - b[2];
        });
        int n = list.size();
        List<List<Integer>> ans = new ArrayList<>();
        for(int i = 0; i<n;){
            int j = i;
            List<Integer> tmp = new ArrayList<>();
            while(j<n && list.get(j)[0] == list.get(i)[0]){
                tmp.add(list.get(j++)[2]);
            }
            ans.add(tmp);
            i=j;
        }
        return ans;
    }

    void dfs(TreeNode root){
        if(root == null) return;
        int[] info = map.get(root); 
        int col = info[0], row = info[1], val = info[2];
        if(root.left != null){
            map.put(root.left,new int[]{col-1,row+1,root.left.val});
            dfs(root.left);
        }
        if(root.right != null){
            map.put(root.right,new int[]{col+1,row+1,root.right.val});
            dfs(root.right);
        }
    }
}

DFS + 优先队列(堆)

class Solution {
    PriorityQueue<int[]> q = new PriorityQueue<>((a, b)->{ // col, row, val
        if (a[0] != b[0]) return a[0] - b[0];
        if (a[1] != b[1]) return a[1] - b[1];
        return a[2] - b[2];
    });
    public List<List<Integer>> verticalTraversal(TreeNode root) {
        int[] info = new int[]{0, 0, root.val};
        q.add(info);
        dfs(root, info);
        List<List<Integer>> ans = new ArrayList<>();
        while (!q.isEmpty()) {
            List<Integer> tmp = new ArrayList<>();
            int[] poll = q.peek();
            while (!q.isEmpty() && q.peek()[0] == poll[0]) tmp.add(q.poll()[2]);
            ans.add(tmp);
        }
        return ans;
    }
    void dfs(TreeNode root, int[] fa) {
        if (root.left != null) {
            int[] linfo = new int[]{fa[0] - 1, fa[1] + 1, root.left.val};
            q.add(linfo);
            dfs(root.left, linfo);
        }
        if (root.right != null) {
            int[] rinfo = new int[]{fa[0] + 1, fa[1] + 1, root.right.val};
            q.add(rinfo);
            dfs(root.right, rinfo);
        }
    }
}

236. 二叉树的最近公共祖先

image-20220810084804935

有以下几种情况:

1.当p、q都在root的子树中,都分别在左右子树,则祖先为两者中深度最高的那个root

2.当p或者q都在一侧,且其中一个在另外一个的左右子树中,则祖先为两者之一

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //当其中一者为空时候
        if(root == null || root == p || root == q) return  root;
        //递归:将根节点变为左子树
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        //递归:根节点变为右子树
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        //当前节点无子节点的时候,遍历右子树
        if(left == null) return  right;
        if(right == null) return left;
        return root;
    }
}
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //当其中一者为空时候
        if(root == null || root == p || root == q) return  root;
        //递归:将根节点变为左子树
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        //递归:根节点变为右子树
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left == null && right == null) {
            return null;
        }
        //当前节点无子节点的时候,遍历右子树
        if(left == null) return  right;
        if(right == null) return left;
        return root;
    }
}

437. 路径总和 III

image-20220810094723077

前缀和:

前缀和(Prefix Sum)的定义为:对于一个给定的数列 A, 它的前缀和数列 S 是通过递推能求出来得 S[i] = \sum_{j = 1}^{i}A[j] 部分和。

前缀和就是从位置1到位置i这个区间内的所有的数字之和。

前缀和的优势:以(o1)的时间复杂度得到某块区间的总和

image-20220810095219174

class Solution {
    public int pathSum(TreeNode root, int targetSum) {
    // key是前缀和, value是大小为key的前缀和出现的次数
        Map<Long , Integer> prefix = new HashMap<Long ,Integer>();
        //前缀和为0的一条路径
        prefix.put(0L,1);
        return dfs(root, prefix, 0, targetSum);

    }

    public int dfs(TreeNode root, Map<Long, Integer> prefix, long curr, int targetSum) {
    	// 1.递归终止条件
        if(root == null) return 0;
        //计算路径次数
        int ret = 0;
        // 当前路径上的和
        curr += root.val;
        ret = prefix.getOrDefault(curr-targetSum,0);
        prefix.put(curr,prefix.getOrDefault(curr , 0) +1);
        ret += dfs(root.left, prefix, curr, targetSum);
        ret += dfs(root.right, prefix,curr,targetSum);
        prefix.put(curr, prefix.getOrDefault(curr, 0) - 1);

        return ret;
    }
}

797. 所有可能的路径

image-20220831143029460

方法一:深度优先搜索
思路和算法

我们可以使用深度优先搜索的方式求出所有可能的路径。具体地,我们从 0号点出发,使用栈记录路径上的点。每次我们遍历到点 n-1,就将栈中记录的路径加入到答案中。

特别地,因为本题中的图为有向无环图(DAG),搜索过程中不会反复遍历同一个点,因此我们无需判断当前点是否遍历过。

class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    Deque<Integer> stack = new ArrayDeque<Integer>();

    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        stack.offerLast(0);
        dfs(graph, 0, graph.length - 1);
        return ans;
    }

    public void dfs(int[][] graph, int x, int n) {
        if (x == n) {
            ans.add(new ArrayList<Integer>(stack));
            return;
        }
        for (int y : graph[x]) {
            stack.offerLast(y);
            dfs(graph, y, n);
            stack.pollLast();
        }
    }
}

image-20220831151342169

BFS:

jdk9的新特性

List接口,Set接口,Map接口:里面增加了一个静态的方法of,可以给集合一次性添加多个元素

使用前提:

​ 当集合中存储的元素个数已经确定了,不再改变时使用。

注意:

​ 1.of方法只适用于List接口,Set接口,Map接口,不适用于接口的实现类

​ 2.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常

​ 3.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常

Arrays.asList()可以插入null,而List.of()不可以

class Solution {
	public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
		List<List<Integer>> res = new ArrayList<>();
		int size = graph.length;
		Queue<List<Integer>> queue = new LinkedList<>();
		queue.offer(List.of(0));
		while (!queue.isEmpty()) {
			int n = queue.size();
			for (int i = 0; i < n; i++) {
				List<Integer> cul = queue.poll();
				int last = cul.get(cul.size() - 1);
				if (last == size - 1) {
					res.add(cul);
					continue;
				}
				int[] dist = graph[last];
				for (int num : dist) {
					List<Integer> list = new ArrayList<>(cul);
					list.add(num);
					queue.offer(list);
				}
			}
		}
		return res;
	}
}
class Solution {
    //记录所有路径
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        LinkedList<Integer> path = new LinkedList<>();
        traverse(graph, 0, path);
        return res;
    }
    //图的遍历框架
    void traverse(int[][] graph, int s, LinkedList<Integer> path) {
        //添加节点s到路径
        path.addLast(s);
        int n = graph.length;
        if (s == n - 1) {
            //到达终点
            res.add(new LinkedList<>(path));
            path.removeLast();
            return;
        }
        //递归每个相邻节点
        for (int v : graph[s]){
            traverse(graph,v,path);
        }
        //从路径移出节点s
        path.removeLast();
    }
}

207. 课程表

image-20220831154619258

646. 最长数对链

image-20220903160659312

解法:动态规划

import java.util.Arrays;

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public int findLongestChain(int[][] pairs) {
        int n = pairs.length;
        Arrays.sort(pairs,(a,b)->a[0]-b[0]);
        int[] dp = new int[n];
        Arrays.fill(dp,1);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (pairs[i][0] > pairs[j][1]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }

            }
        }
        return dp[n - 1];
    }
}
//leetcode submit region end(Prohibit modification and deletion)

贪心

import java.util.Arrays;

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public int findLongestChain(int[][] pairs) {
        int cur = Integer.MIN_VALUE,res = 0;
        Arrays.sort(pairs, (a, b) -> a[1] - b[1]);
        for (int[] p : pairs) {
            if (cur < p[0]) {
                cur = p[1];
                res++;
            }
        }
        return res;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

652. 寻找重复的子树

image-20220905112126698

DFS + 哈希表
设计递归函数 String dfs(TreeNode root),含义为返回以传入参数 root 为根节点的子树所对应的指纹标识。

对于标识的设计只需使用 “_” 分割不同的节点值,同时对空节点进行保留(定义为空串 " ")即可。

使用哈希表记录每个标识(子树)出现次数,当出现次数为 22(首次判定为重复出现)时,将该节点加入答案。

class Solution {
    Map<String,Integer> map = new HashMap<>();
    List<TreeNode> list = new ArrayList<>();
    public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
        dfs(root);
        return list;
    }
    //设计递归函数,含义为返回以传入参数root为根结点的子树
    String dfs(TreeNode root) {
        if (root == null) {
            return " ";
        }
        StringBuilder sb = new StringBuilder();
        //对于标识的设计只需使用 "_" 分割不同的节点值,同时对空节点进行保留(定义为空串 " ")
        sb.append(root.val).append("_");
        sb.append(dfs(root.left)).append(dfs(root.right));
        String key = sb.toString();
        map.put(key, map.getOrDefault(key,0) + 1);
        //使用哈希表记录每个标识(子树)出现次数,当出现次数为 22(首次判定为重复出现)时,将该节点加入答案。
        if (map.get(key) == 2) {
            list.add(root);
        }
        return key;

    }
}

米哈游面试题

image-20220914215325555

image-20220914215456144

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param preorder int整型一维数组
     * @param inorder int整型一维数组
     * @return int整型
     */
    public class TreeNode{
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(){
        }
        public TreeNode(int val) {
            this.val = val;
        }

        public TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }
    public int maxPathSum (int[] preorder, int[] inorder) {
        // write code here
        TreeNode root = buildTree(preorder, inorder);
         return  maxGains(root);
    }

    public  int maxGains(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        int maxSum = 0;
        if (root != null) {
            queue.add(root);
        }
        while (!queue.isEmpty()) {
            int max = Integer.MIN_VALUE;
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode q = queue.poll();
                max = Math.max(max, q.val);
                if (q.left != null) {
                    queue.add(q.left);
                }
                if (q.right != null) {
                    queue.add(q.right);
                }
            }
            maxSum += max;
        }
        return maxSum;
    }
    private  TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder.length == 0 || inorder.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[0]);
        for (int i = 1; i < preorder.length; i++) {
            if (preorder[0] == inorder[i]) {
                int[] leftPreorder = Arrays.copyOfRange(preorder, 1, 1 + i);
                int[] rightPreorder = Arrays.copyOfRange(preorder, i+1, preorder.length);
                int[] leftInorder = Arrays.copyOfRange(inorder, 0, i);
                int[] rightInorder = Arrays.copyOfRange(inorder, i + 1, inorder.length);
                root.left = buildTree(leftPreorder, leftInorder);
                root.right = buildTree(rightPreorder, rightInorder);
                break;
            }
        }
        return root;
    }
}

网格系列

DFS 的基本结构
网格结构要比二叉树结构稍微复杂一些,它其实是一种简化版的图结构。要写好网格上的 DFS 遍历,我们首先要理解二叉树上的 DFS 遍历方法,再类比写出网格结构上的 DFS 遍历。我们写的二叉树 DFS 遍历一般是这样的:

void traverse(TreeNode root) {
    // 判断 base case
    if (root == null) {
        return;
    }
    // 访问两个相邻结点:左子结点、右子结点
    traverse(root.left);
    traverse(root.right);
}

可以看到,二叉树的 DFS 有两个要素:「访问相邻结点」和「判断 base case」。

网格 DFS 遍历的框架代码:

void dfs(int[][] grid, int r, int c) {
    // 判断 base case
    // 如果坐标 (r, c) 超出了网格范围,直接返回
    if (!inArea(grid, r, c)) {
        return;
    }
    // 访问上、下、左、右四个相邻结点
    dfs(grid, r - 1, c);
    dfs(grid, r + 1, c);
    dfs(grid, r, c - 1);
    dfs(grid, r, c + 1);
}

// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
    return 0 <= r && r < grid.length 
        	&& 0 <= c && c < grid[0].length;
}

如何避免这样的重复遍历呢?答案是标记已经遍历过的格子。以岛屿问题为例,我们需要在所有值为 1 的陆地格子上做 DFS 遍历。每走过一个陆地格子,就把格子的值改为 2,这样当我们遇到 2 的时候,就知道这是遍历过的格子了。也就是说,每个格子可能取三个值:

0 —— 海洋格子
1 —— 陆地格子(未遍历过)
2 —— 陆地格子(已遍历过)

我们在框架代码中加入避免重复遍历的语句:

void dfs(int[][] grid, int r, int c) {
    // 判断 base case
    if (!inArea(grid, r, c)) {
        return;
    }
    // 如果这个格子不是岛屿,直接返回
    if (grid[r][c] != 1) {
        return;
    }
    grid[r][c] = 2; // 将格子标记为「已遍历过」
    
    // 访问上、下、左、右四个相邻结点
    dfs(grid, r - 1, c);
    dfs(grid, r + 1, c);
    dfs(grid, r, c - 1);
    dfs(grid, r, c + 1);
}

// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
    return 0 <= r && r < grid.length 
        	&& 0 <= c && c < grid[0].length;
}

200. 岛屿数量

image-20220905135826457

思路一:深度优先遍历DFS

image-20220905160103625

class Solution {
    public int numIslands(char[][] grid) {
        int count = 0;
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                if(grid[i][j] == '1'){
                    dfs(grid, i, j);
                    count++;
                }
            }
        }
        return count;
    }
    private void dfs(char[][] grid, int i, int j){
        if(i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') return;
        grid[i][j] = '0';
        dfs(grid, i + 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i - 1, j);
        dfs(grid, i, j - 1);
    }
}


思路二:广度优先遍历 BFS

  • 主循环和思路一类似,不同点是在于搜索某岛屿边界的方法不同。

  • bfs 方法:

    • 借用一个队列 queue,判断队列首部节点 (i, j) 是否未越界且为 1:
      • 若是则置零(删除岛屿节点),并将此节点上下左右节点 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 加入队列;
      • 若不是则跳过此节点;
  • 循环 pop 队列首节点,直到整个队列为空,此时已经遍历完此岛屿。

lass Solution {
    public int numIslands(char[][] grid) {
        int count = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                if (grid[i][j] == '1') {
                    dfs(grid, i, j);
                    count++;
                }
            }
        }
        return count;
    }

    private void dfs(char[][] grid, int i, int j) {
        Queue<int[]> list = new LinkedList<>();
        list.add(new int[]{i, j});
        while (!list.isEmpty()) {
            int[] cur = list.remove();
            i = cur[0];
            j = cur[1];
            if (0 <= i && i < grid.length
                    && 0 <= j && j < grid[0].length && grid[i][j] == '1') {
                grid[i][j] = '0';
                list.add(new int[]{i + 1, j});
                list.add(new int[]{i - 1, j});
                list.add(new int[]{i, j - 1});
                list.add(new int[]{i, j + 1});
            }
        }
    }
}

463. 岛屿的周长

image-20220905143013685

可以看到,dfs 函数直接返回有这几种情况:

!inArea(grid, r, c),即坐标 (r, c) 超出了网格的范围,也就是我所说的「先污染后治理」的情况
grid[r][c] != 1,即当前格子不是岛屿格子,这又分为两种情况:
grid[r][c] == 0,当前格子是海洋格子
grid[r][c] == 2,当前格子是已遍历的陆地格子

class Solution {
  public int islandPerimeter(int[][] grid) {
  	for (int r = 0; r < grid.length; r++) {
  		for (int c = 0; c < grid[0].length; c++) {
  			if (grid[r][c] == 1) {
  			// 题目限制只有一个岛屿,计算一个即可
  				return dfs(grid, r, c);
  			}
  		}
  	}
  return 0;
  }

  int dfs(int[][] grid, int r, int c) {
      // 函数因为「坐标 (r, c) 超出网格范围」返回,对应一条黄色的边
      if (!inArea(grid, r, c)) {
          return 1;
      }
      // 函数因为「当前格子是海洋格子」返回,对应一条蓝色的边
      if (grid[r][c] == 0) {
          return 1;
      }
      // 函数因为「当前格子是已遍历的陆地格子」返回,和周长没关系
      if (grid[r][c] != 1) {
          return 0;
      }
      grid[r][c] = 2;
      return dfs(grid, r - 1, c)
          + dfs(grid, r + 1, c)
          + dfs(grid, r, c - 1)
          + dfs(grid, r, c + 1);
  }

  // 判断坐标 (r, c) 是否在网格中
  boolean inArea(int[][] grid, int r, int c) {
      return 0 <= r && r < grid.length 
            && 0 <= c && c < grid[0].length;
  }

}

695. 岛屿的最大面积

image-20220905143530020

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int res = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                if (grid[i][j] == 1) {
                    res = Math.max(res, dfs(i, j, grid));
                }
            }
        }
        return res;
    }

    private int dfs(int i, int j, int[][] grid) {
        //当超出岛屿边界(上下左右)的时候,就直接退出,特别要加上当遍历到海洋的时候也要退出,
        if (i < 0 || j < 0 || i >= grid.length || j >= grid[i].length
                || grid[i][j] == 0) {
            return 0;
        }
        //将陆地改为海洋,防止重复陆地重复遍历。
        grid[i][j] = 0;
        //定义一个变量表示岛屿的面积,就是包含几个陆地
        int num = 1;
        num += dfs(i + 1, j, grid);
        num += dfs(i - 1, j, grid);
        num += dfs(i , j +1, grid);
        num += dfs(i , j -1, grid);
        return num;
    }
}
class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int res = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                if (grid[i][j] == 1) {
                    res = Math.max(res, dfsArea(grid,i,j));
                }
            }
        }
        return res;
    }

    int dfsArea(int[][] grid, int r, int c) {
        if (!inArea(grid, r, c)) {
            return 0;
        }
        if (grid[r][c] != 1) {
            return 0;
        }
        grid[r][c] = 2;
        return 1
                + dfsArea(grid, r - 1, c)
                + dfsArea(grid, r + 1, c)
                + dfsArea(grid, r, c - 1)
                + dfsArea(grid, r, c + 1);
    }

    boolean inArea(int[][] grid, int r, int c) {
        return 0<= r && r< grid.length
                && 0<= c && c < grid[0].length;
    }

}

934. 最短的桥

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rn4xE7OT-1667540758235)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20221025175042516.png)]

class Solution {
    int[][] dirs = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
    Queue<int[]> queue = new ArrayDeque<int[]>();
    public int shortestBridge(int[][] grid) {
        int r = grid.length;
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < r; j++) {
                if (grid[i][j] == 1) {
                    dfs(grid, i, j);
                    int step = 0;
                    while (!queue.isEmpty()) {
                        int len = queue.size();
                        for (int k = 0; k < len; k++) {
                            int[] cell = queue.poll();
                            int x = cell[0] , y = cell[1];
                            for (int d = 0; d < 4; d++) {
                                int newX = x + dirs[d][0];
                                int newY = y + dirs[d][1];
                                if (inArea(grid, newX, newY)) {
                                    if (grid[newX][newY] == 0) {
                                        queue.offer(new int[]{newX, newY});
                                        grid[newX][newY] = -1;
                                    }else if (grid[newX][newY] == 1) {
                                        return step;
                                    }
                                }
                            }
                        }
                        step++;
                    }
                }
            }
        }
        return 0;
    }

    void dfs(int[][] grid, int m, int n) {
        if (!inArea(grid, m, n) || grid[m][n] != 1) {
            return;
        }
        queue.offer(new int[] {m,n});
        grid[m][n] = -1;
        dfs(grid, m-1, n);
        dfs(grid, m+1, n);
        dfs(grid, m, n - 1);
        dfs(grid, m, n + 1);
    }

    boolean inArea(int[][] grid, int m, int n) {
        return m>= 0 && m<grid.length
                && n>= 0 && n< grid[0].length;
    }
}

2059. 转化数字的最小运算数

image-20220922175855411

class Solution {
    public int minimumOperations(int[] nums, int start, int goal) {
        Deque<Integer> deque = new ArrayDeque<>();
        Map<Integer, Integer> map = new HashMap<>();
        //将start入队列
        deque.addLast(start);
        map.put(start, 0);
        while (!deque.isEmpty()) {
            //推出队首元素
            int cur = deque.pollFirst();
            //step=运算次数
            int step = map.get(cur);
            for (int i : nums) {
                //创建新的数组,x+当前数字等三种运算
                int[] res = new int[]{cur + i, cur - i, cur ^ i};
                //判断res是否等于目标值
                for (int next : res) {
                    //等于目标值,则step+1
                    if (next == goal) {
                        return step + 1;
                    }
                    //超出范围就继续
                    if (next < 0 || next > 1000) {
                        continue;
                    }
                    //如果哈希表中有运算结果,则继续
                    if (map.containsKey(next)) {
                        continue;
                    }
                    //将res的元素加入哈希表中,不管等于与否,都要加1,
                    map.put(next, step + 1);
                    //将当前操作得到的数加入队列中
                    deque.addLast(next);
                }
            }
        }
        return -1;
    }
}

3.二分查找

有序数组的二分查找:

public int search(int[] nums, int target) {
    int lo = 0, hi = nums.length - 1, mid = 0;
    while (lo <= hi) {
        mid = lo + ((hi - lo) >> 1);
        if (nums[mid] == target) {
            return mid;
        }
        if (nums[mid] < target) {
            lo = mid + 1;
        } else {
            hi = mid - 1;
        }
    }
    return -1;
}

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 ...;
}

重点技巧:

  • 计算 mid 时需要防止溢出:left + (right - left) / 2
  • 不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节
  • 当初始化 right 的赋值是 nums.length - 1时候,while 循环的条件中是 <=
  • 当初始化 right 的赋值是 nums.length 时候,while 循环的条件中是 <
  • 如果 nums 中不存在 target 这个值时,在返回的时候额外判断一下 nums[left] 是否等于 target 就行了,在检查 nums[left] 之前需要额外判断一下,防止索引越界:
while (left < right) {
    //...
}
// 此时 target 比所有数都大,返回 -1
if (left == nums.length) return -1;
// 判断一下 nums[left] 是不是 target
return nums[left] == target ? left : -1;

第一个,最基本的二分查找算法

因为我们初始化 right = nums.length - 1
所以决定了我们的「搜索区间」是 [left, right]
所以决定了 while (left <= right)
同时也决定了 left = mid+1 和 right = mid-1

因为我们只需找到一个 target 的索引即可
所以当 nums[mid] == target 时可以立即返回

第二个,寻找左侧边界的二分查找

因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid + 1 和 right = mid

因为我们需找到 target 的最左侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧右侧边界以锁定左侧边界

第三个,寻找右侧边界的二分查找

因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid + 1 和 right = mid

因为我们需找到 target 的最右侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧左侧边界以锁定右侧边界

又因为收紧左侧边界时必须 left = mid + 1
所以最后无论返回 left 还是 right,必须减一

704. 二分查找

image-20220805210439940

class Solution {
    public int 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){
                return mid;
            }else if(nums[mid]<target){
                left = mid +1;
            }else if(nums[mid]>target){
                right = mid-1;
            }
        }
        return -1;

    }
}

34. 在排序数组中查找元素的第一个和最后一个位置

image-20220805210727854

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] res = new int[]{-1,-1};
        int n = nums.length;
        if(n == 0) return res;

        int left = 0 ,right = n-1;
        while(left < right){
            int mid = left + right >> 1;
            if(nums[mid] >= target){
                right = mid;
            }else {
                left = mid +1;
            }
        }

        if(nums[left] != target){
            return res;
        }else{
            res[0] = left;
            left = 0; 
            right = n -1;
            while(left < right){
                int mid = left + right + 1 >> 1;
                if(nums[mid] <= target){
                    left = mid;
                } else {
                    right = mid -1;
                }
            }
            res[1] = left;
        return res;
        }
    }
}

1608. 特殊数组的特征值

image-20220912231217868

class Solution {
    public int specialArray(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        for (int x = 0; x < 1010; x++) {
            int l = 0, r = n - 1;
            while (l < r) {
                int mid = l + r >> 1;
                if (nums[mid] >= x) r = mid;
                else l = mid + 1;
            }
            if (nums[r] >= x && x == n - r) return x;
        }
        return -1;
    }
}

33. 搜索旋转排序数组

image-20220922232007130

法1:

image-20220923131948864

class Solution {
    public int search(int[] nums, int target) {
        int lo = 0, hi = nums.length-1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            // 先根据 nums[0] 与 target 的关系判断目标值是在左半段还是右半段
            if (target >= nums[0]) {
                // 目标值在左半段时,若 mid 在右半段,则将 mid 索引的值改成 inf
                if (nums[mid] < nums[0]) {
                    nums[mid] = Integer.MAX_VALUE;
                }
            }else {
                // 目标值在右半段时,若 mid 在左半段,则将 mid 索引的值改成 -inf
                if (nums[mid] >= nums[0]) {
                    nums[mid] = Integer.MIN_VALUE;
                }
            }
            if (nums[mid] < target) {
                lo = mid + 1;
            }else {
                hi = mid - 1;
            }
        }
        return -1;
    }
}

法二:

class Solution {
    public int search(int[] nums, int target) {
        int lo = 0, hi = nums.length - 1, mid = 0;
        while (lo <= hi) {
            mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            // 先根据 nums[mid] 与 nums[lo] 的关系判断 mid 是在左段还是右段
            if (nums[mid] >= nums[lo]) {
                // 再判断 target 是在 mid 的左边还是右边,从而调整左右边界 lo 和 hi
                if (target >= nums[lo] && target < nums[mid]) {
                    hi = mid - 1;
                } else {
                    lo = mid + 1;
                }
            } else {
                if (target > nums[mid] && target <= nums[hi]) {
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
        }
        return -1;
    }
}

81. 搜索旋转排序数组 II

image-20220923131542523

class Solution {
    public boolean search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int lo = 0, hi = nums.length - 1, mid = 0;
        while (lo <= hi) {
            mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) {
                return true;
            }
            if (nums[mid] == nums[lo]) {
                lo++;
                continue;
            }
            if (nums[mid] > nums[lo]) {
                if (target >= nums[lo] && target < nums[mid]) {
                    hi = mid - 1;
                }else {
                    lo = mid + 1;
                }
            }else {
                if (target > nums[mid] && target <= nums[hi]) {
                    lo = mid + 1;
                }else {
                    hi = mid -1;
                }
            }
        }
        return false;
    }
}

153. 寻找旋转排序数组中的最小值

image-20220923131633684

class Solution {
    public int findMin(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int mid = left + right + 1 >> 1;
            if (nums[mid] >= nums[0]) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return right + 1 < nums.length ? nums[right + 1] : nums[0];
    }
}
class Solution {
    public int findMin(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < nums[right]) {
                right = mid;
            }else {
                left = mid + 1;
            }
        }
        return nums[left];
    }
}

154. 寻找旋转排序数组中的最小值 II

image-20220923145703555

image-20220923145733426

class Solution {
    public int findMin(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < nums[right]) {
                right = mid;
            }else if (nums[mid]> nums[right]){
                left = mid + 1;
            }else {
                right = right - 1;
            }
        }
        return nums[left];
    }
}

1640. 能否连接形成数组

image-20220923132608944

1011. 在 D 天内送达包裹的能力

image-20220923150002626

class Solution {
    public int shipWithinDays(int[] weights, int days) {
        int max = 0, sum = 0;
        for (int w : weights) {
            max = Math.max(max, w);
            sum += w;
        }
        int l = max, r = sum;
        while (l < r) {
            int mid = l + r >> 1;
            if (check(weights, mid, days)) {
                r = mid;
            } else {
                l = mid+1;
            }
        }
        return r;
    }

    boolean check(int[] weights, int m, int days) {
        int n = weights.length;
        int cnt = 1;
        for (int i = 1, sum = weights[0]; i < n; sum = 0, cnt++) {
            while (i < n && sum + weights[i] <= m) {
                sum += weights[i];
                i++;
            }
        }
        return cnt - 1 <= days;
    }
}

1482. 制作 m 束花所需的最少天数

image-20220923154038802

2035. 将数组分成两个数组并最小化数组和的差

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXppJmQ9-1667540758241)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20220927092304738.png)]

class Solution {
    final int INF = 2_000_000_000;
    int[] ns;
    int n;
    ArrayList<TreeSet<Integer>> ls;
    int ans;

    private void ldfs(int idx, int sum, int cnt) {
        if (idx >= n) {
            ls.get(cnt).add(sum);
            return;
        }
        ldfs(idx + 1, sum - ns[idx], cnt);
        ldfs(idx + 1, sum + ns[idx], cnt + 1);
    }

    private void rdfs(int idx, int sum, int cnt) {
        if (idx >= 2 * n) {
            Integer x1 = ls.get(n - cnt).floor(-sum);
            Integer x2 = ls.get(n - cnt).ceiling(-sum);
            if (x1 != null) {
                ans = Math.min(ans, Math.abs(sum + x1));
            }
            if (x2 != null) {
                ans = Math.min(ans, Math.abs(sum + x2));
            }
            return;
        }
        rdfs(idx + 1, sum - ns[idx], cnt);
        rdfs(idx + 1, sum + ns[idx], cnt + 1);
    }

    public int minimumDifference(int[] _ns) {
        ns = _ns;
        n = ns.length / 2;
        ans = INF;
        ls = new ArrayList<>();
        for (int i = 0; i < n + 1; i++) {
            ls.add(new TreeSet<>());
        }
        Arrays.sort(ns); // 加速TreeSet内部排序
        ldfs(0, 0, 0);
        rdfs(n, 0, 0);
        return ans;
    }

}

4.LRU算法

146. LRU 缓存

image-20220805152247620

class LRUCache {
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode() {}
        public DLinkedNode(int _key,int _value){
            key = _key;
            value = _value;
        }
    }
    private Map<Integer,DLinkedNode> cache = new HashMap<Integer,DLinkedNode>();
    private int size;
    private int capacity;
    private DLinkedNode head,tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.next = head;
    }
    
    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if(node == null) {
            return -1;
        }
        moveToHead(node);
        return node.value;

    }
    
    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if(node == null){
            DLinkedNode newNode = new DLinkedNode(key,value);
            //添加进哈希表
            cache.put(key,newNode);
            //添加至双向链表得头部
            addToHead(newNode);
            ++size;
            if(size>capacity){
                //如果超出容量,删除双向链表的尾部节点
                DLinkedNode tail =removeTail();
                //删除哈希表中对应的项
                cache.remove(tail.key);
                --size;
            }
        }else{
            node.value = value;
            moveToHead(node);
        }


    }
    private void addToHead(DLinkedNode node){
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }
    private void removeNode(DLinkedNode node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node){
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail(){
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}


5.栈

栈是一种线性结构,它只能从一端添加元素,也只能从一端取出元素(这一端称之为栈顶)。

Stack这种数据结构用途很广泛,在计算机的使用中,大量的运用了栈,比如编译器中的词法分析器、Java虚拟机、软件中的撤销操作(Undo)、浏览器中的回退操作,编译器中的函数调用实现等等。

接口说明复杂度
void push(E e)向栈中加入元素O(1) 均摊
E pop()弹出栈顶元素O(1) 均摊
E peek()查看栈顶元素O(1)
int getSize()获取栈中元素个数O(1)
boolean isEmpty()判断栈是否为空O(1)

img

辅助栈

维护一个能够在O(1)内完成取序列最小值、数字入栈、数字出栈的数据结构

对于此问题,可以通过再维护一个按照当前序列输入顺序的最小值栈(即定义栈顶的元素为此前元素的最小值,每次入栈时与栈顶比较后选择较小值入栈。)

#include<stack>
using namespace std;
const int maxn = 1e6 + 5;


class MinStack {
public:
    stack<int> s, mins;
    
    MinStack() {
        while(!s.empty()) s.pop();
        while(!mins.empty()) mins.pop();
    }
    
    void push(int x) {
        s.push(x);
        int nowmin;
        if(!mins.empty()){
            nowmin = mins.top();
            nowmin = min(nowmin, x);
        }
        else nowmin = x;
        mins.push(nowmin);
    }
    
    void pop() {
        s.pop(), mins.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int getMin() {
        return mins.top();
    }
};
Support_Stack

对顶栈

*维护一个支持移动操作数据位置的序列(即在当前位置插入、删除、求此前序列最大值(or前缀和的最大值等)和移动当前操作位置等操作)

对于需要在序列中间操作的数据结构,可以考虑建立两个对口数据结构,比如对顶栈

移动操作位置只需要不断将一个栈内元素进入另一个栈中即可(插入删除入栈出栈即可,求最大值等属性可以通过辅助数组在每次元素改变时同步更新。)

#include<bits/stdc++.h>
using namespace std;


int getmax[(unsigned)1e6 + 1];
int getsum[(unsigned)1e6 + 1];
int now = 0, temp;

int main(){
    ios::sync_with_stdio(false);

    memset(getmax, -0x3f, sizeof getmax);
    stack<int> left, right;
    int q;

    cin>>q;
    while(q--){
        char c;
        cin>>c;
        switch(c){
            case 'I':int x; cin>>x; left.push(x), now++;getsum[now] = getsum[now-1] + x; getmax[now] = max(getmax[now-1], getsum[now]); break;
            case 'D':if(!left.empty()) left.pop(), now--; break;
            case 'L':if(!left.empty()){right.push(left.top()); left.pop(); now--;} break;
            case 'R':if(!right.empty()){left.push(right.top()); right.pop(); now++; getsum[now] = getsum[now-1] + left.top(); getmax[now] = max(getmax[now-1], getsum[now]);} break;
            case 'Q':int k; cin>>k; cout<<getmax[k]<<endl; break;
        }
    }
    return 0;
}

Opposite_top_Stack

144. 二叉树的前序遍历

二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树

image-20220808095744346

递归:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        preorder(root,res);
        return res;

    }
    public void preorder(TreeNode root ,List<Integer> res) {
        if(root == null) return;
        res.add(root.val);
        preorder(root.left,res);
        preorder(root.right,res);
    }
}

迭代:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Deque<TreeNode> stk = new LinkedList<TreeNode>();
        while(root != null || !stk.isEmpty()){
            while(root != null){
            //数组里加入当前节点的值
                res.add(root.val);
                //当前节点进栈
                stk.push(root);
                //节点变为左子树
                root = root.left;
            }
            将当前节点出栈
            root = stk.pop();
            当前节点变为右子树
            root = root.right;
        }
        return res;
    }
}

94. 二叉树的中序遍历

二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树

image-20220805181050252

递归:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        inorder(root,res);
        return res;

    }
    public void inorder(TreeNode root ,List<Integer> res) {
        if(root == null) return ;
        
        inorder(root.left,res);
        res.add(root.val);
        inorder(root.right,res);
    }
}

迭代:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Deque<TreeNode> stk = new LinkedList<TreeNode>();
        //根节点不为空 或者 堆中不为空
        while(root != null || !stk.isEmpty()){
            //根节点不为空
            while(root != null){
                    //往堆里加入当前根节点
                    stk.push(root);
                    //节点变为左子树,加入左子树
                    root = root.left;
                }
                //将堆上的顶点推出去
                root = stk.pop();
                //将推出去的数加入列表
                res.add(root.val);
                //加入右子树
                root= root.right;
        }
        return res;
    }
}

145. 二叉树的后序遍历

二叉树的后序遍历:按照访问左子树——右子树——根节点的方式遍历这棵树

image-20220808102824922

递归:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        postorder(root,res);
        return res;
    }
    public void postorder(TreeNode root ,List<Integer> res){
        if(root == null){
            return;
        }
        postorder(root.left,res);
        postorder(root.right,res);
        res.add(root.val);
    }
}

迭代:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if(root == null) return res;
        Deque<TreeNode> stk = new LinkedList<TreeNode>();
        //用prev记录访问历史,在回溯到父节点的时候,判断上个访问的节点是否为右子树
        TreeNode prev = null;
        while(root != null || !stk.isEmpty()){
            while(root != null){
                stk.push(root);
                root = root.left;
            }
            root = stk.pop();
            //确定是否有右子树或者右子树是否被访问过
            if(root.right == null || root.right == prev){
                res.add(root.val);
                //更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
                prev = root;
                root = null;
                
            }else {
            		//如果右子树没有被访问,那么将当前节点入栈,访问右子树
                stk.push(root);
                root = root.right;
            }
        }
        return res;
    }
}

102. 二叉树的层序遍历

层序遍历一个二叉树:就是从左到右一层一层的去遍历二叉树。

image-20220808111018495


234. 回文链表

image-20220805181605712

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null){
            return true;
        }
        ListNode slow = head, fast =head;
        ListNode pre = head, prepre = null;
        while(fast != null && fast.next != null){
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
            pre.next = prepre;
            prepre = pre;
        }
        if(fast != null){
            slow = slow.next;
        }
        while(pre != null && slow != null){
            if(pre.val != slow.val){
                return false;
            }
            pre = pre.next;
            slow = slow.next;
        }
        return true;
    }
}

114. 二叉树展开为链表

image-20220808095007137

  1. 将左子树插入到右子树的地方
  2. 将原来的右子树接到左子树的最右边节点
  3. 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
  4. image-20220808210358776
class Solution {
    public void flatten(TreeNode root) {
        //当根节点不为空时
        while (root != null) {
            // 如果左子树部为空时候,root指向右子树
            if (root.left == null) {
                root = root.right;
            } else {
                //找到左子树最右边的节点
                TreeNode pre = root.left;
                while (pre.right != null) {
//                    当右边的节点不为空的时候,节点到最后边的节点
                    pre = pre.right;
                }
                //将原来的右子树连接到左子树的最右边的节点
                pre.right = root.right;
                //将左子树插到右子树的地方
                root.right = root.left;
                //令左子树等于空
                root.left = null;
                //考虑下一个节点
                root = root.right;
            }
        }
    }
}

155. 最小栈

image-20220810105051090

设计一个数据结构,使得每个元素 a 与其相应的最小值 m时刻保持一一对应。因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。

  • 当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;
  • 当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出;
  • 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。

class MinStack {
    Deque<Integer> xStack;
    Deque<Integer> minStack;

    public MinStack() {
        //创建一个存储输入的值的栈
        xStack = new LinkedList<Integer>();
        //创建一个最小辅助栈
        minStack = new LinkedList<Integer>();
        //将最大值先入辅助栈
        minStack.push(Integer.MAX_VALUE);
    }
    
    public void push(int x) {
        //先将入栈
        xStack.push(x);
        //比较辅助栈的当前栈底元素和x的比较大小,最小的入栈
        minStack.push(Math.min(minStack.peek(),x));
    }
    
    public void pop() {
        //当前栈顶元素出栈
        xStack.pop();
        //辅助栈出栈
        minStack.pop();
    }
    
    public int top() {
        //当前值的栈顶元素
        return xStack.peek();
    }
    
    public int getMin() {
        return minStack.peek();

    }
}

20. 有效的括号

image-20220810122552924

解题思路

这道题让我们验证输入的字符串是否为括号字符串,包括大括号,中括号和小括号。

这里我们使用

  • 遍历输入字符串
  • 如果当前字符为左半边括号时,则将其压入栈中
  • 如果遇到右半边括号时,分类讨论:
  • 1)如栈不为空且为对应的左半边括号,则取出栈顶元素,继续循环
  • 2)若此时栈为空,则直接返回 false
  • 3)若不为对应的左半边括号,反之返回 false

img

Java中的Java.util.Stack.isEmpty()方法用于检查和验证Stack是否为空。如果堆栈为空,则返回True,否则返回False。

class Solution {
    public boolean isValid(String s) {
        int len = s.length();
        //长度为奇数直接为判错
        if (len % 2 == 1) {
            return false;
        }
        //使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号
        Map<Character,Character> pairs = new HashMap<Character,Character>() {{
            put(')','(');
            put(']','[');
            put('}','{');
        }};
        //设置一个辅助栈
        Deque<Character> stack = new LinkedList<Character>();
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            //如果哈希表中存在当前字符的key时
            if (pairs.containsKey(c)) {
            //栈为空 或者 当前栈顶不等于当前key对应的value时候
                if (stack.isEmpty() || stack.peek() != pairs.get(c)) {
                    return false;
                }
                //存在则将字符出栈
                stack.pop();
            } else {
                //不存在,则将字符压入栈中
                stack.push(c);
            }
        }
        return stack.isEmpty();

    }
}
    public static boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (ch == '(' || ch == '[' || ch == '{') {
                stack.push(ch);
            } else {
                if (stack.isEmpty()) {
                    return false;
                }
                char topChar = stack.pop();
                if (ch == ')' && topChar != '(') {
                    return false;
                } else if (ch == ']' && topChar != '[') {
                    return false;
                } else if (ch == '}' && topChar != '{') {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }

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

image-20220811132952453

解题思路

A栈用来处理入栈(push)操作,B栈用来处理出栈(pop)操作。一个元素进入 A栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入B 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。

  • push 元素时,始终是进入栈,pop 和 peek 元素时始终是走出栈。
  • pop 和 peek 操作,如果出栈为空,则需要从入栈将所有元素移到出栈,也就是调换顺序,比如开始push的顺序是 3-2-1,1 是最先进入的元素,则到出栈的顺序是 1-2-3,那 pop 操作拿到的就是 1,满足了先进先出的特点。
  • pop 和 peek 操作,如果出栈不为空,则不需要从入栈中移到数据到出栈。

img

class CQueue {
    LinkedList<Integer> A,B;

    public CQueue() {
        A = new LinkedList<Integer>();
        B = new LinkedList<Integer>();

    }
    
    public void appendTail(int value) {
        //addLast()方法用于在LinkedList的末尾插入特定元素
        //往A栈中加入元素
        A.addLast(value);
    }
    
    public int deleteHead() {
        //如果B不为空,则移除B中栈顶元素
        if(!B.isEmpty()) return B.removeLast();
        //A中为空 返回-1
        if(A.isEmpty()) return -1;
        //当A中不为空,将A中的栈顶元素加入栈B中
        while (!A.isEmpty()) {
            B.addLast(A.removeLast());
        }
        //LinkedList.removeLast()方法用于从LinkedList中删除最后一个元素。删除元素后,返回LinkedList中的最后一个元素
        //返回栈B的最后移除的元素
        return B.removeLast();
    }
}

剑指 Offer 31. 栈的压入、弹出序列

image-20220811141900121

解题思路

借用一个辅助的栈,遍历压栈顺序,先将 第一个放入栈中,这里是 1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是 4,很显然 1≠4 ,所以需要继续压栈,直到相等以后开始出栈。

出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack<Integer> stack = new Stack<Integer>();
        int i = 0;
        for (int num : pushed) {
            stack.push(num);
            while (!stack.isEmpty() && stack.peek() == popped[i]) {
                stack.pop();
                i++;
            }
        }
        return stack.isEmpty();
    }

}

剑指 Offer 30. 包含min函数的栈

image-20220811155746523

解题思路

使用两个 stack,一个作为数据栈,另一个作为辅助栈。其中 数据栈 用于存储所有数据,而 辅助栈 用于存储最小值。

  1. 入栈的时候:首先往空的数据栈里压入数字 3 ,此时 3 是最小值,所以把最小值压入辅助栈。接下来往数据栈里压入数字 4 。由于 4 大于之前的最小值,因此只要入数据栈,此时辅助栈把记录最小值栈的栈顶元素再压一次(辅助栈保持跟数据站一样的元素数量)。
  2. 出栈的时候:数据栈进行了弹栈操作,那么记录最小值元素的辅助栈同样也进行弹栈操作即可。
  3. 获得栈顶元素的时候:直接返回数据栈的栈顶元素。
  4. 栈最小元素:直接返回辅助栈的栈顶元素,但是不要弹出。
class MinStack {
    Deque<Integer> xStack;
    Deque<Integer> minStack;

    /** initialize your data structure here. */
    public MinStack() {
        xStack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        minStack.push(Integer.MAX_VALUE);
    }
    
    public void push(int x) {
        xStack.push(x);
        minStack.push(Math.min(minStack.peek(),x));
    }
    
    public void pop() {
        xStack.pop();
        minStack.pop();
    }
    
    public int top() {
         return xStack.peek();
    }
    
    public int min() {
        return  minStack.peek();
    }
}

32. 最长有效括号

image-20220811184439863

栈:

class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        stack.push(-1);
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else {
                stack.pop();
                if (stack.isEmpty()) {
                    stack.push(i);
                } else {
                    maxans = Math.max(maxans, i - stack.peek());
                }
            }
        }
        return maxans;
    }
}

动态规划

class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        int[] dp = new int[s.length()];
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
                maxans = Math.max(maxans, dp[i]);
            }
        }
        return maxans;
    }
}

42. 接雨水

image-20220811201543122

动态规划

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        if(n == 0) return 0;
        int[] leftMax = new int[n];
        leftMax[0] = height[0];
        for (int i = 1; i < n; ++i) {
            leftMax[i] = Math.max(leftMax[i-1],height[i]);
        }
        int[] rightMax = new int[n];
        rightMax[n-1] = height[n-1];
        for (int i = n-2; i >= 0; --i) {
            rightMax[i] = Math.max(rightMax[i+1], height[i]);
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans += Math.min(leftMax[i],rightMax[i]) - height[i];
        }
        return ans;
    }
}

public int trap(int[] height) {
    int sum = 0;
    int max = getMax(height);//找到最大的高度,以便遍历。
    for (int i = 1; i <= max; i++) {
        boolean isStart = false; //标记是否开始更新 temp
        int temp_sum = 0;
        for (int j = 0; j < height.length; j++) {
            if (isStart && height[j] < i) {
                temp_sum++;
            }
            if (height[j] >= i) {
                sum = sum + temp_sum;
                temp_sum = 0;
                isStart = true;
            }
        }
    }
    return sum;
}
private int getMax(int[] height) {
		int max = 0;
		for (int i = 0; i < height.length; i++) {
			if (height[i] > max) {
				max = height[i];
			}
		}
		return max;
}

1124. 表现良好的最长时间段

image-20220815074920582

  • 前缀和 + 单调栈
class Solution {
    public int longestWPI(int[] hours) {
        int maxInterval = 0;
        int n = hours.length;
        //创建前缀和数组,因为计算n-1个数字之和,则总共为n+1
        int[] sums = new int[n+1];

        for(int i= 0;i<n; i++){
            //将数组分为两部分:大于8的为1,小于等于8的为-1
            int score = hours[i] >8 ? 1 : -1;
            //计算前缀和数组的每个元素,则这样表现良好的时间段为大于0的子数组
            sums[i+1] = sums[i] + score;
        }
        //设置一个单调栈存储可能是最长的时间段的下标,因此元素单调递减
        Deque<Integer> stack = new ArrayDeque<Integer>();
        for(int i = 0; i<= n; i++){
            int sum = sums[i];
            //栈为空或者栈顶的下标对应的元素大于sums[i]时候,将i入栈
            //遍历结束之后,栈内的每个下标 i 都满足对于任意小于 i 的下标 k 都有sums[k] > sums[i]
            if(stack.isEmpty() || sums[stack.peek()] > sum){
                stack.push(i);
            }
        }
        //然后从右到左遍历数组sums,对于每个下标j,找到最小的下标i使得sums[i]<sums[j]
        for(int j = n;j>=0;j--){
            int num = sums[j];
            //当栈不为空且栈顶下标对应的元素小于 sums[j] 时,令栈顶下标为 i,将 i 出栈,并用 j - i 
            //更新最长时间段,重复该操作直到栈为空或者栈顶下标对应的元素大于sums[j] 
            while(!stack.isEmpty() && sums[stack.peek()] < num){
                int interval  = j-stack.pop();
                maxInterval = Math.max(maxInterval,interval);
            }
        }
        return maxInterval;
    }
}

1047. 删除字符串中的所有相邻重复项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4j8H4voB-1667540758250)(/Users/xing/Library/Application Support/typora-user-images/image-20220907155122844.png)]

单调栈

定义:

单调栈,顾名思义就是栈内元素单调按照递增(递减)顺序排列的栈。

单调递增栈:

  • ①在一个队列中针对每一个元素从它右边寻找第一个比它小的元素
  • ②在一个队列中针对每一个元素从它左边寻找第一个比它小的元素

单调递减栈:

  • ①在一个队列中针对每一个元素从它右边寻找第一个比它大的元素
  • ②在一个队列中针对每一个元素从它左边寻找第一个比它大的元素

单调栈何时用:为任意一个元素找左边和右边第一个比自己大/小的位置用单调栈.

由于每个元素最多各自进出栈一次,复杂度是O(n).

​ 以数组nums[] = {3,2,1,4,6,5,7}举例,那么我们的单调递增栈为:

Stack.peek()与Stack.pop()

  • peek():返回栈顶的值 ;不改变栈的值,查看栈顶的对象而不移除它。

  • pop():返回栈顶的值 ;会把栈顶的值删除。

poll与pop

  • poll:Queue(队列)的一个方法,获取并移除此队列的头,如果此队列为空,则返回null。

  • pop:Stack(栈)的方法,移除堆栈顶部的对象,并作为此函数的值返回该对象 。

496. 下一个更大元素 I

image-20220910135039027

当题目出现「找到最近一个比其大的元素」的字眼时,自然会想到「单调栈」。

具体的,由于我们目标是找到某个数其在 nums2 的右边中第一个比其大的数,因此我们可以对nums2 进行逆序遍历。

我们在遍历 nums2 时,实时维护一个单调栈,当我们遍历到元素nums2[i] 时,可以先将栈顶中比 nums2[i] 小的元素出栈,最终结果有两种可能:

  1. 栈为空,说明 nums2[i] 之前(右边)没有比其大的数;

  2. 栈不为空, 此时栈顶元素为nums2[i] 在 nums2 中(右边)最近的比其大的数。

再利用数组中数值各不相同,在遍历nums2 的同时,使用哈希表记录每个nums2[i] 对应目标值是多少即可。

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        int n = nums1.length,m = nums2.length;
        int[] res = new int[n];
        Deque<Integer> stack = new ArrayDeque<>();
        //创建哈希表,存储当前nums1的值,和最后返回的值
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = m - 1; i >= 0; i--) {
            int x = nums2[i];
            //当栈不为空,以及栈顶的值小于左边的值时,将栈顶推出
            while (!stack.isEmpty() && stack.peek() <= x) {
                stack.poll();
            }
            //加入map中
            map.put(x, stack.isEmpty() ? -1 : stack.peek());
            stack.push(x);
        }
        for (int i = 0; i < n; i++) {
            res[i] = map.get(nums1[i]);
        }
        return res;

    }
}

503. 下一个更大元素 II

image-20220910143845729

一般是通过 % 运算符求模(余数),来模拟环形特效:

int[] arr = {1,2,3,4,5};
int n = arr.length, index = 0;
while (true) {
    // 在环形数组中转圈
    print(arr[index % n]);
    index++;
}
class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        Stack<Integer> stack = new Stack<Integer>();
        Arrays.fill(res, -1);
        for (int i = 0; i < 2 * n - 1; i++) {
            while (!stack.isEmpty() && nums[stack.peek()] < nums[i % n]) {
                //将栈顶值对应索引的数组 等于当前nums的值
                res[stack.pop()] = nums[i % n];
            }
            stack.push(i % n);
        }
        return res;
    }
}

1475. 商品折扣后的最终价格

image-20220910153801795

class Solution {
    public int[] finalPrices(int[] prices) {
        int n = prices.length;
        int[] nums = new int[n];
        Stack<Integer> stack = new Stack<Integer>();
        for (int i = 0; i <n; i++) {
            while (!stack.isEmpty() && prices[stack.peek()] >= prices[i]) {
                int index = stack.pop();
                nums[index] = prices[index] -prices[i];
            }
            stack.push(i);
            nums[i] = prices[i];
        }
        return nums;
    }
}

285 · 高楼大厦

https://www.lintcode.com/problem/285/description

image-20220910125026191

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;

public class Solution {
    /**
     * @param arr: the height of all buildings
     * @return: how many buildings can he see at the location of each building
     */
    public int[] tallBuilding(int[] arr) {
        // Write your code here.
        int[] res = new int[arr.length];
        Arrays.fill(res, 1);
        
        Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < arr.length; i++) {
            res[i] += stack.size();
            while (!stack.isEmpty() && stack.peek() <= arr[i]) {
                stack.pop();
            }
            
            stack.push(arr[i]);
        }
        
        // 清空栈,再从右向左遍历数组
        stack.clear();
    
        for (int i = arr.length - 1; i >= 0; i--) {
            res[i] += stack.size();
            while (!stack.isEmpty() && stack.peek() <= arr[i]) {
                stack.pop();
            }
    
            stack.push(arr[i]);
        }
        
        return res;
    }
}

739. 每日温度

image-20220811161423235

具体操作如下:

遍历整个数组,如果栈不空,且当前数字大于栈顶元素,那么如果直接入栈的话就不是 递减栈 ,所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。

继续看新的栈顶元素,直到当前数字小于等于栈顶元素停止,然后将数字入栈,这样就可以一直保持递减栈,且每个数字和第一个大于它的数的距离也可以算出来。

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int len =temperatures.length;
        int[] ans = new int[len];
        Deque<Integer> stack = new LinkedList<Integer>();
        for (int i = 0; i < len; i++) {
            int temp = temperatures[i];
            while (!stack.isEmpty() && temp > temperatures[stack.peek()]) {
                int prevIndex = stack.pop();
                ans[prevIndex] = i - prevIndex;
            }
            stack.push(i);
        }
        return ans;
    }
}

哈希表 +单调栈

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
       int n = temperatures.length;
       Stack<Integer> stack = new Stack<Integer>();
       HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();
       for (int i = 0; i < n; i++) {
           while (!stack.isEmpty() && temperatures[i] >temperatures[stack.peek()]){
               //将当前天气温度更小的出栈,以及索引放入哈希表中
               map.put(stack.pop() , i);
           }
           //将索引入栈
           stack.push(i);
       }
       int[] ans = new int[n];
       for (int i = 0; i < n; i++) {
           ans[i] = map.containsKey(i) ? map.get(i) - i : 0;
       }
       return ans;
    }
}

907. 子数组的最小值之和

image-20220910154604904

怎么由一个数组变成多个数组:

image-20220910163415569

class Solution {
    int MOD = (int)1e9+7;
    public int sumSubarrayMins(int[] arr) {
        int n = arr.length;
        int[] l = new int[n], r = new int[n];
        Arrays.fill(l, -1); Arrays.fill(r, n);
        Deque<Integer> d = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            while (!d.isEmpty() && arr[d.peekLast()] >= arr[i]) r[d.pollLast()] = i;
            d.addLast(i);
        }
        d.clear();
        for (int i = n - 1; i >= 0; i--) {
            while (!d.isEmpty() && arr[d.peekLast()] > arr[i]) l[d.pollLast()] = i;
            d.addLast(i);
        }
        long ans = 0;
        for (int i = 0; i < n; i++) {
            int a = l[i], b = r[i];
            ans += (i - a) * 1L * (b - i) % MOD * arr[i] % MOD;
            ans %= MOD;
        }
        return (int) ans;
    }
}

6.滑动窗口

题目 题解 难度 推荐指数

  1. 串联所有单词的子串 LeetCode 题解链接 困难 🤩🤩
  2. 重复的DNA序列 LeetCode 题解链接 中等 🤩🤩🤩🤩
  3. 存在重复元素 III LeetCode 题解链接 中等 🤩🤩🤩
  4. 替换后的最长重复字符 LeetCode 题解链接 中等 🤩🤩🤩🤩
  5. 滑动窗口中位数 LeetCode 题解链接 困难 🤩🤩🤩🤩
  6. 字符串的排列 LeetCode 题解链接 中等 🤩🤩🤩
  7. 最长和谐子序列 LeetCode 题解链接 简单 🤩🤩🤩🤩
  8. 子数组最大平均数 I LeetCode 题解链接 简单 🤩🤩🤩🤩🤩
  9. K 个不同整数的子数组 LeetCode 题解链接 困难 🤩🤩🤩🤩
  10. 最大连续1的个数 III LeetCode 题解链接 中等 🤩🤩🤩
  11. 爱生气的书店老板 LeetCode 题解链接 中等 🤩🤩🤩
  12. 尽可能使字符串相等 LeetCode 题解链接 中等 🤩🤩🤩
  13. 可获得的最大点数 LeetCode 题解链接 中等 🤩🤩🤩🤩
  14. 绝对差不超过限制的最长连续子数组 LeetCode 题解链接 中等 🤩🤩🤩
  15. 可见点的最大数目 LeetCode 题解链接 困难 🤩🤩🤩🤩
  16. 最高频元素的频数 LeetCode 题解链接 中等 🤩🤩🤩

基本概念

滑动窗口是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。

分类:窗口有两类,一种是固定大小类的窗口,一类是大小动态变化的窗口。

应用

利用滑动窗口获取平滑的数据,如一段连续时间的数据平均值,能够有更好的稳定性,如温度监测。

什么情况可以用滑动窗口来解决实际问题呢?

  1. 一般给出的数据结构是数组或者字符串
  2. 求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
  3. 该问题本身可以通过暴力求解

核心思路

窗口的形成

在具体使用之前,我们知道窗口实际是两个指针之间形成的区域,那关键就是这两个指针是如何移动的。

  1. 初始时,左右指针left,right都指向第0个元素,窗口为[left,right),注意这里是左闭右开,因此初始窗口[0,0)区间没有元素,符合我们的初始定义
  2. 开始循环遍历整个
  3. 然后right指针开始向右移动一个长度,并更新窗口内的区间数据
  4. 当窗口区间的数据满足我们的要求时,右指针right就保持不变,左指针left开始移动,直到移动到一个不再满足要求的区间时,left不再移动位置
  5. 执行第2步

preview

这中间,窗口的更新与维护是很重要的一环,新元素加入窗口,旧元素移出窗口,都需要及时地更新与这个窗口范围相关的数据。

上述说明主要是两个while循环,可以简单抽象成一个模板如下:

int left = 0,right =0;
while(right指针未越界){
  char ch = arr[right++];
  //右指针移动,更新窗口
  ...
  
  //窗口数据满足条件 对于固定窗口而言,就是窗口的大小>=固定值;对于动态窗口,就是从left出发,窗口不断扩充,第一次满足题意的位置
  while(窗口数据满足条件){
  	//记录或者更新全局数据
  	...
  	
  	//右指针不动,左指针开始移动一位
  	char tmp = arr[left++];
  	
  	//左指针移动,窗口缩小,更新窗口数据
  	...
  }
  //返回结果
  ...
}

219. 存在重复元素 II

image-20220815075114723

  • 哈希
class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
    //因为HashSet里不允许有重复元素,所以用哈希Set
        HashSet<Integer> set = new HashSet<>();
        for(int i = 0;i<nums.length;i++){
            //判断是否存在重复元素,
            if(set.contains(nums[i])){
                //存在则返回true
                return true;
            }
            //不存在则将数组下标加入哈希表中
            set.add(nums[i]);
            //当哈希表的长度大于K时
            if(set.size()>k){
                //移除最先进来的数组下标
                set.remove(nums[i - k]);
            }
        }
        return false;
    }
}
  • 滑动窗口
class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        int n = nums.length;
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < n; i++) {
            if (i > k) set.remove(nums[i - k - 1]);
            if (set.contains(nums[i])) return true;
            set.add(nums[i]);
        }
        return false;
    }
}

3. 无重复字符的最长子串

image-20220815095635776

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        if(s.length() == 0) return  0;
        HashMap<Character,Integer> map = new HashMap<>();
        int max = 0;
        int left = 0;
        for (int i = 0; i < s.length(); i++) {
            if (map.containsKey(s.charAt(i))) {
                left = Math.max(left, map.get(s.charAt(i)) + 1);
            }
            map.put(s.charAt(i), i);
            max = Math.max(max, i - left + 1);
        }
        return max;
    }
}

643. 子数组最大平均数 I

image-20220815184432505

class Solution {
    public double findMaxAverage(int[] nums, int k) {
        int n = nums.length;
        int sum = 0;
        //计算固定窗口长度为K的总和
        for (int i = 0; i < k; i++) {
            sum += nums[i];
        }
        int maxSum = sum;
        //利用for循环,将窗口向右滑动
        for (int i = k; i < n; i++) {
            sum = sum - nums[i-k] + nums[i];
            maxSum = Math.max(maxSum, sum);
        }
        //注意返回值是浮点型,因此计算除法时需要进行数据类型转换。
        return 1.0 * maxSum / k;

    }
}

76. 最小覆盖子串

image-20220815184859664

class Solution {
    public String minWindow(String s, String t) {
        //1.维护两个map记录窗口中的符合条件的字符以及need的字符
        Map<Character,Integer> window = new HashMap<>();
        Map<Character,Integer> need = new HashMap<>();//need中存储的是需要的字符以及需要的对应的数量
        for(char c : t.toCharArray())
            need.put(c,need.getOrDefault(c,0)+1);
        int left = 0,right = 0;//双指针
        int count = 0;//count记录当前窗口中符合need要求的字符的数量,当count == need.size()时即可shrik窗口
        int start = 0;//start表示符合最优解的substring的起始位序
        int len = Integer.MAX_VALUE;//len用来记录最终窗口的长度,并且以len作比较,淘汰选出最小的substring的len

        //一次遍历找“可行解”
        while(right < s.length()){
            //更新窗口
            char c = s.charAt(right);
            right++;//窗口扩大
            // window.put(c,window.getOrDefault(c,0)+1);其实并不需要将s中所有的都加入windowsmap,只需要将need中的加入即可
            if(need.containsKey(c)){
                window.put(c,window.getOrDefault(c,0)+1);
                if(need.get(c).equals(window.get(c))){
                    count++;
                }
            }
            //System.out.println****Debug位置
            //shrink左边界,找符合条件的最优解
            while(count == need.size()){
                if(right - left < len){//不断“打擂”找满足条件的len最短值,并记录最短的子串的起始位序start
                    len = right - left;
                    start = left;
                }
                //更新窗口——这段代码逻辑几乎完全同上面的更新窗口
                char d = s.charAt(left);
                left++;//窗口缩小
                if(need.containsKey(d)){
                    //window.put(d,window.get(d)-1);——bug:若一进去就将window对应的键值缩小,就永远不会满足下面的if,while也会一直执行,知道left越界,因此,尽管和上面对窗口的处理几乎一样,但是这个处理的顺序还是很关键的!要细心!
                    if(need.get(d).equals(window.get(d))){
                        count--;
                    }
                    window.put(d,window.get(d)-1);
                    
                }
            }
        }
        return len == Integer.MAX_VALUE ? "" : s.substring(start,start+len);
    }
    
}

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
       String M =in.nextLine();
       String N = in.nextLine();
        System.out.println(minWindows(M,N));
    }

    public static String minWindows(String s, String t) {
        if (s.length() < t.length() || t.length() == 0) {
            return "";
        }
        Map<Character,Integer> difference = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            difference.put(t.charAt(i), difference.getOrDefault(t.charAt(i), 0) + 1);

        }
        int l = 0, r = 0, minLen = Integer.MAX_VALUE,
                minLeft = 0, minRight = 0, count = difference.size();
        while (r < s.length()) {
            char addChar = s.charAt(r);
            if (difference.containsKey(addChar)) {
                int curCount = difference.get(addChar);
                difference.put(addChar, curCount-1);
                if (curCount == 1) {
                    count--;
                }
            }
            while (count == 0) {
                if (r - l + 1 < minLen) {
                    minLen = r - l + 1;
                    minLeft = l;
                    minRight = r;
                }
                char delCh = s.charAt(l);
                if (difference.containsKey(delCh)) {
                    int curCount = difference.get(delCh);
                    difference.put(delCh, curCount + 1);
                    if (curCount == 0) {
                        count++;
                    }
                }
                l++;
            }
            r++;
        }
        return minLen == Integer.MAX_VALUE ? "" :s.substring(minLeft,minRight+1);
    }


}

239. 滑动窗口最大值

image-20220815204248511

单调队列
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || nums.length < 2) return nums;
        // 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
        LinkedList<Integer> queue = new LinkedList();
        // 结果数组
        int[] result = new int[nums.length-k+1];
        // 遍历nums数组
        for(int i = 0;i < nums.length;i++){
            // 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
            while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
                queue.pollLast();
            }
            // 添加当前值对应的数组下标
            queue.addLast(i);
            // 判断当前队列中队首的值是否有效
            if(queue.peek() <= i-k){
                queue.poll();   
            } 
            // 当窗口长度为k时 保存当前窗口中最大值
            if(i+1 >= k){
                result[i+1-k] = nums[queue.peek()];
            }
        }
        return result;
    }
}

股票问题

image-20220906153709989

image-20220906153734696

121. 买卖股票的最佳时机

image-20220906111245248

class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        int cost = prices[0];
        for (int i = 1; i < prices.length; i++) {
            profit = Math.max(profit,prices[i]-cost);
            cost= Math.min(prices[i],cost);
        }
        return profit;
    }
}

122. 买卖股票的最佳时机 II

image-20220905232428084

贪心算法

class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        for (int i = 1; i < prices.length; i++) {
            int tmp = prices[i] - prices[i - 1];
            if (tmp > 0) profit += tmp;
        }
        return profit;
    }
}

动态规划

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int profit = 0 ,cost = -prices[0];
        for (int i = 1; i < n; i++) {
            int newProfit = Math.max(profit, cost + prices[i]);
            int newCost = Math.max(cost, profit - prices[i]);
            profit = newProfit;
            cost= newCost;
        }
        return profit;
    }
}

123. 买卖股票的最佳时机 III

image-20220906115939682

方法一:动态规划
思路与算法

由于我们最多可以完成两笔交易,因此在任意一天结束之后,我们会处于以下五个状态中的一种:

  • 未进行过任何操作;

  • 只进行过一次买操作;

  • 进行了一次买操作和一次卖操作,即完成了一笔交易;

  • 在完成了一笔交易的前提下,进行了第二次买操作;

  • 完成了全部两笔交易。

class Solution {
    public int maxProfit(int[] prices) {
        int n  = prices.length;
        int buy1 = -prices[0], sell1 = 0;
        int buy2 = -prices[0], sell2 = 0;
        for (int i = 1; i < n; i++) {
            buy1 = Math.max(buy1, -prices[i]);
            sell1 = Math.max(sell1, buy1 + prices[i]);
            buy2 = Math.max(buy2, sell1 - prices[i]);
            sell2 = Math.max(sell2, buy2 + prices[i]);
        }
        return sell2;
    }
}

188. 买卖股票的最佳时机 IV

image-20220906152406075

class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if (n < 2) {
            return 0;
        }
        //因为一次交易至少涉及两天,所以如果k大于总天数的一半,就直接取天数一半即可,多余的交易次数是无意义的
        k = Math.min(k, n / 2);
        /*dp定义:dp[i][j][k]代表
        第i天交易了k次时的最大利润,其中j代表当天是否持有股票,0不持有,1持有*/
        int[][][] dp = new int[n][2][k + 1];
        for (int i = 0; i <= k; i++) {
            dp[0][0][i] = 0;
            dp[0][1][i] = -prices[0];
        }
            /*状态方程:
        dp[i][0][k],当天不持有股票时,看前一天的股票持有情况
        dp[i][1][k],当天持有股票时,看前一天的股票持有情况*/
        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= k; j++) {
                dp[i][0][j] = Math.max(dp[i - 1][0][j], dp[i - 1][1][j] + prices[i]);
                dp[i][1][j] = Math.max(dp[i - 1][1][j], dp[i - 1][0][j-1] - prices[i]);
            }
        }
        return dp[n - 1][0][k];

    }
}

309. 最佳买卖股票时机含冷冻期

image-20220906155206080

dp[i][0] 代表第 i 天没有持有股票
dp[i][1] 代表第 i 天持有股票
dp[i][2] 代表第 i 天是冷冻期

image-20220906160417890

class Solution {
    public int maxProfit(int[] prices) {
        //创建二维数组,用来代表三种状态
        int[][] dp = new int[prices.length][3];
        dp[0][1] = -prices[0];
        for (int i = 1; i < dp.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][2] - prices[i]);
            dp[i][2] = dp[i - 1][0];
        }
        return dp[prices.length - 1][0];
    }
}

优化空间

class Solution {
    public int maxProfit(int[] prices) {
        int dp0 = 0,dp1 = -prices[0],dp2 = 0;
        for (int price : prices) {
            int temp = dp0;
            dp0 = Math.max(dp0, dp1 + price);
            dp1 = Math.max(dp1, dp2 - price);
            dp2 = temp;
        }
        return dp0;
    }
}

714. 买卖股票的最佳时机含手续费

image-20220906163043444

image-20220906161751211

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee); 
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        }
        return dp[n - 1][0];
    }
}

优化后

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int[] dp = new int[2];
        dp[0] = 0;
        dp[1] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[0] = Math.max(dp[0], dp[1] + prices[i] - fee);
            dp[1] = Math.max(dp[1], dp[0] - prices[i]);
        }
        return dp[0];
    }
}

7.分冶算法

剑指 Offer 07. 重建二叉树

image-20220817081243863

思路:

  1. 根据中序遍历和前序遍历确定根节点的值,因此确定左右子树对应的序列
  2. 再用同样的方法递归,构建左右子树

分治算法解析:

  • 递推参数: 根节点在前序遍历的索引 root 、子树在中序遍历的左边界 left 、子树在中序遍历的右边界 right ;
  • 终止条件: 当 left > right ,代表已经越过叶节点,此时返回 null ;
  • 递推工作:
  1. 建立根节点 node : 节点值为 preorder[root] ;

  2. 划分左右子树: 查找根节点在中序遍历 inorder 中的索引 i ;
    为了提升效率,本文使用哈希表 dic 存储中序遍历的值与索引的映射,查找操作的时间复杂度为O(1) ;

  3. 构建左右子树: 开启左右子树递归;

    TIPS: i - left + root + 1含义为 根节点索引 + 左子树长度 + 1

根节点索引中序遍历左边界中序遍历右边界
左子树root + 1lefti - 1
右子树i - left + root + 1i + 1right
class Solution {
	//保留的先序遍历,方便递归时依据索引查看先序遍历的值
    int[] preorder;
    
    HashMap<Integer, Integer> dic = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        //将中序遍历的值及索引放在map中,方便递归时获取左子树与右子树的数量及其根的索引
        for(int i = 0; i < inorder.length; i++)
            dic.put(inorder[i], i);
        return recur(0, 0, inorder.length - 1);
    }
    TreeNode recur(int root, int left, int right) {
    	// 递归终止
        if(left > right) return null;                          
         // 建立根节点
        TreeNode node = new TreeNode(preorder[root]);         
        // 划分根节点、左子树、右子树
        //获取在中序遍历中根节点所在索引,以方便获取左子树的数量
        int i = dic.get(preorder[root]);                       
         // 开启左子树递归
         //左子树的根的索引为先序中的根节点+1 
        //递归左子树的左边界为原来的中序left
        //递归左子树的右边界为中序中的根节点索引-1
        node.left = recur(root + 1, left, i - 1);             
        // 开启右子树递归
         //右子树的根的索引为先序中的 当前根位置 + 左子树的数量 + 1
         //递归右子树的左边界为中序中当前根节点+1
        //递归右子树的右边界为中序中原来右子树的边界
        node.right = recur(root + i - left + 1, i + 1, right); 
        // 回溯返回根节点
        return node;                                           
    }
}

8.贪心算法

思想

1.贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
2.贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素。
3.当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响。贪心算法对每个子问题的解决方案都做出选择,不能回退。
4.贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。
5.实际上,贪心算法适用的情贪心算法(贪婪算法)况很少。一般对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可以做出判断。

该算法存在的问题
1.不能保证求得的最后解是最佳的
2.不能用来求最大值或最小值的问题
3.只能求满足某些约束条件的可行解的范围

剑指 Offer 14- I. 剪绳子

image-20220817095303786

动态规划

class Solution {
    public int cuttingRope(int n) {
        /*
        dp五部曲:
        1.状态定义:dp[i]为长度为i的绳子剪成m段最大乘积为dp[i]
        2.状态转移:dp[i]有两种途径可以转移得到
            2.1 由前一个dp[j]*(i-j)得到,即前面剪了>=2段,后面再剪一段,此时的乘积个数>=3个
            2.2 前面单独成一段,后面剩下的单独成一段,乘积为j*(i-j),乘积个数为2
            两种情况中取大的值作为dp[i]的值,同时应该遍历所有j,j∈[1,i-1],取最大值
        3.初始化:初始化dp[1]=1即可
        4.遍历顺序:显然为正序遍历
        5.返回坐标:返回dp[n]
        */
        // 定义dp数组
        int[] dp = new int[n + 1];
        // 初始化
        dp[1] = 1;  // 指长度为1的单独乘积为1
        // 遍历[2,n]的每个状态
        for(int i = 2; i <= n; i++) {
            for(int j = 1; j <= i - 1; j++) {
                // 求出两种转移情况(乘积个数为2和2以上)的最大值
                int tmp = Math.max(dp[j] * (i - j), j * (i - j));
                dp[i] = Math.max(tmp, dp[i]);
            }
        }
        return dp[n];
    }
}
class Solution {
    public int cuttingRope(int n) {
        if(n <2) return 0;
        if(n ==2) return 1;
        if(n == 3) return 2;

        int[] ans = new int[n+1];
        int j = 0;
        while(j<=3){
            ans[j] = j;
            j++;
        }
        int max = 0;
        for(int i = 4;i<= n; i++){
            max = 0;
            for(j =1;j<= i/2;++j){
                int res = ans[j] * ans[i-j];
                max = Math.max(max,res);
                ans[i] = max;
            }
        }
        return max;

    }
}

数学公式

推论一: 将绳子 以相等的长度等分为多段 ,得到的乘积最大。

推论二: 尽可能将绳子以长度 33 等分为多段时,乘积最大。

class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) return n - 1;
        int a = n / 3, b = n % 3;
        if(b == 0) return (int)Math.pow(3, a);
        if(b == 1) return (int)Math.pow(3, a - 1) * 4;
        return (int)Math.pow(3, a) * 2;
    }
}

剑指 Offer 14- II. 剪绳子 II

image-20220817103903157

class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) return n - 1;
        long res=1L;
        int p=(int)1e9+7;
        //贪心算法,优先切三,其次切二
        while(n>4){
            res=res*3%p;
            n-=3;
        }
        //出来循环只有三种情况,分别是n=2、3、4
        return (int)(res*n%p);
    }
}

11. 盛最多水的容器

image-20220817104904602

class Solution {
    public int maxArea(int[] height) {
        int l = 0 ,r = height.length-1;
        int max = 0;
        while(r > l){
            int s = (r - l) * Math.min(height[r],height[l]);
            max = Math.max(max ,s );
            if(height[l] <height[r]){
                l++;
            }else{
                r--;
            }
        }
        return max;
    }
}
class Solution {
    public int maxArea(int[] height) {
        int i = 0, j = height.length - 1, res = 0;
        while(i < j) {
            res = height[i] < height[j] ? 
                Math.max(res, (j - i) * height[i++]): 
                Math.max(res, (j - i) * height[j--]); 
        }
        return res;
    }
}

670. 最大交换

image-20220913195736021

选择排序

class Solution {
    public int maximumSwap(int num) {
        if (num % 10 == num) return num;

        char[] arr = String.valueOf(num).toCharArray();
        for (int i = 0; i < arr.length; i++) {
            // 从i后面选择一个最大的,这个最大的离i越远越好,比如1993,1交换第二个9更优,所以j倒序遍历
            int maxIndex = i;
            for (int j = arr.length - 1; j >= i + 1; j--) {
                if (arr[j] > arr[maxIndex]) {
                    maxIndex = j;
                }
            }

            if (maxIndex != i) {
                char tmp = arr[i];
                arr[i] = arr[maxIndex];
                arr[maxIndex] = tmp;
                return Integer.parseInt(new String(arr));
            }
        }

        return num;
    }
}

2139. 得到目标值的最少行动次数

image-20220919214405239

image-20220919214753652

//ACM 模式
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String s = sc.next();
        String[] str = s.split(",");
        int target= Integer.parseInt(str[0]);
        int maxDoubles = Integer.parseInt(str[1]);
        System.out.println(minMoves(target, maxDoubles));
    }

    static int minMoves(int target, double maxDoubles) {
        int temp =0;
        while (target != 1) {
            if (maxDoubles == 0) {
                temp = temp + target -1;
                break;
            }
            if (target % 2 != 0) {
                temp++;
                target--;
            }else {
                maxDoubles--;
                temp++;
                target/=2;
            }
        }
        return temp;
    }
}

12. 整数转罗马数字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0rrTWvi-1667540758262)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20221025175845309.png)]

class Solution {
    public String intToRoman(int num) {
        int[] nums = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
        String[] romans = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
        StringBuilder sb = new StringBuilder();
        int index = 0;
        while (index < 13) {
            while (num >= nums[index]) {
                sb.append(romans[index]);
                num -= nums[index];
            }
            index++;
        }
        return sb.toString();
    }
}

9.回溯算法

一、什么是回溯算法

回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

回溯算法实际上是一个类似枚举的深度优先搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回(也就是递归返回),尝试别的路径。

二、回溯算法思想

回溯法一般都用在要给出多个可以实现最终条件的解的最终形式。回溯法要求对解要添加一些约束条件。总的来说,如果要解决一个回溯法的问题,通常要确定三个元素:

1、选择。对于每个特定的解,肯定是由一步步构建而来的,而每一步怎么构建,肯定都是有限个选择,要怎么选择,这个要知道;同时,在编程时候要定下,优先或合法的每一步选择的顺序,一般是通过多个if或者for循环来排列。

2、条件。对于每个特定的解的某一步,他必然要符合某个解要求符合的条件,如果不符合条件,就要回溯,其实回溯也就是递归调用的返回。

3、结束。当到达一个特定结束条件时候,就认为这个一步步构建的解是符合要求的解了。把解存下来或者打印出来。对于这一步来说,有时候也可以另外写一个issolution函数来进行判断。注意,当到达第三步后,有时候还需要构建一个数据结构,把符合要求的解存起来,便于当得到所有解后,把解空间输出来。这个数据结构必须是全局的,作为参数之一传递给递归函数。

三、递归函数的参数的选择,要遵循四个原则

1、必须要有一个临时变量(可以就直接传递一个字面量或者常量进去)传递不完整的解,因为每一步选择后,暂时还没构成完整的解,这个时候这个选择的不完整解,也要想办法传递给递归函数。也就是,把每次递归的不同情况传递给递归调用的函数。

2、可以有一个全局变量,用来存储完整的每个解,一般是个集合容器(也不一定要有这样一个变量,因为每次符合结束条件,不完整解就是完整解了,直接打印即可)。

3、最重要的一点,一定要在参数设计中,可以得到结束条件。一个选择是可以传递一个量n,也许是数组的长度,也许是数量,等等。

4、要保证递归函数返回后,状态可以恢复到递归前,以此达到真正回溯。

回溯法(Back Tracking Method)(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。

可以把回溯法看成是递归调用的一种特殊形式。

代码方面,回溯算法的框架:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。

总结就是:

循环 + 递归 = 回溯

引言点击跳转

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。

许多复杂的,规模较大的问题都可以使用回溯法,有 “通用解题方法” 的美称。

算法思想

回溯(backtracking) 是一种系统地搜索问题解答的方法。为了实现回溯,首先需要为问题定义一个解空间(solution space),这个空间必须至少包含问题的一个解(可能是最优的)。下一步是组织解空间以便它能被容易地搜索。典型的组织方法是图 (迷宫问题) 或树 (N 皇后问题)。一旦定义了解空间的组织方法,这个空间即可按深度优先的方法从开始节点进行搜索。

回溯方法的步骤如下:

  • 定义一个解空间,它包含问题的解。
  • 用适于搜索的方式组织该空间。
  • 用深度优先法搜索该空间,利用限界函数避免移动到不可能产生解的子空间。

回溯算法的一个有趣的特性是在搜索执行的同时产生解空间。在搜索期间的任何时刻,仅保留从开始节点到当前节点的路径。因此,回溯算法的空间需求为 O(从开始节点起最长路径的长度)。这个特性非常重要,因为解空间的大小通常是最长路径长度的指数或阶乘。所以如果要存储全部解空间的话,再多的空间也不够用。

算法应用

回溯算法的求解过程实质上是一个先序遍历一棵 “状态树” 的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中.

  • 幂集问题 (组合问题) 求含 N 个元素的集合的幂集。
如对于集合A={1,2,3},则A的幂集为
p(A)={{1,2,3},{1,2},{1,3},{1},{2,3},{2},{3},Φ}

幂集的每个元素是一个集合,它或是空集,或含集合 A 中的一个元素,或含 A 中的两个元素,或者等于集合 A。反之,集合 A 中的每一个元素,它只有两种状态:属于幂集的元素集,或不属于幂集元素集。则求幂集 P(A)的元素的过程可看成是依次对集合 A 中元素进行 “取” 或 “舍” 的过程,并且可以用一棵状态树来表示。求幂集元素的过程即为先序遍历这棵状态树的过程。

img

解决一个回溯问题,实际上就是一个决策树的遍历过程,站在回溯树的一个节点上,你只需要思考 3 个问题:

1、路径:也就是已经做出的选择。

2、选择列表:也就是你当前可以做的选择。

3、结束条件:也就是到达决策树底层,无法再做选择的条件。

代码方面,回溯算法的框架:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

46. 全排列

img

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        //如果数组为空,返回空
        if (len== 0) {
            return res;
        }
        //创建布尔值,路径中的元素会被标记为true,避免重复使用
        boolean[] used = new boolean[len];
        //创建一个队列,记录路径
        Deque<Integer> path = new ArrayDeque<>(len);
        dfs(nums, len, 0, path, used, res);
        return res;
    }

    private void dfs(int[] nums, int len, int depth,
                     Deque<Integer> path, boolean[] used,
                     List<List<Integer>> res) {
        //如果搜到的深度为最底层的话
        if (depth == len) {
            //则将此时的队列经过的路径的值加入动态数组里
            res.add(new ArrayList<>(path));
            return;
        }
        //遍历每层
        for (int i = 0; i < len; i++) {
            //如果used【i】为false;就是尚未搜索
            if (!used[i]) {
                //则加入队列的队尾
                path.addLast(nums[i]);
                //将used变为true
                used[i] = true;
                接着递归下一层
                dfs(nums, len, depth+1, path, used, res);
               //撤销选择,移除最后队列的元素
                used[i] = false;
                path.removeLast();
            }
        }

    }
}

79. 单词搜索

image-20220905162155656

698. 划分为k个相等的子集

image-20220905125241077

10.双指针

19. 删除链表的倒数第 N 个结点

image-20220903165416935


/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode prev = new ListNode(0);
        prev.next = head;
        ListNode start = prev,end = prev;
        //start指针先向前移动n步
        while (n != 0){
            start = start.next;
            n--;
        }
        //后面start和end 共同向前移动
        //当start到达尾部时候,end的位置恰好为倒数第n个结点
        while (start.next != null) {
            start = start.next;
            end = end.next;
        }
        //删除前一个结点
        end.next = end.next.next;
        //因为head有可能是被删掉的节点,所以返回prev.next
        return prev.next;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

17. 电话号码的字母组合

image-20220903165656638

class Solution {
    public List<String> letterCombinations(String digits) {
        if(digits==null || digits.length()==0) {
            return new ArrayList<String>();
        }
        //一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
        //这里也可以用map,用数组可以更节省点内存
        String[] letter_map = {
                " ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"
        };
        List<String> res = new ArrayList<>();
        //先往队列中加入一个空字符
        res.add("");
        for(int i=0;i<digits.length();i++) {
            //由当前遍历到的字符,取字典表中查找对应的字符串
            String letters = letter_map[digits.charAt(i)-'0'];
            int size = res.size();
            //计算出队列长度后,将队列中的每个元素挨个拿出来
            for(int j=0;j<size;j++) {
                //每次都从队列中拿出第一个元素
                String tmp = res.remove(0);
                //然后跟"def"这样的字符串拼接,并再次放到队列中
                for(int k=0;k<letters.length();k++) {
                    res.add(tmp+letters.charAt(k));
                }
            }
        }
        return res;
    }
}
class Solution {
	//一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
	//这里也可以用map,用数组可以更节省点内存
	String[] letter_map = {" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
	public List<String> letterCombinations(String digits) {
		//注意边界条件
		if(digits==null || digits.length()==0) {
			return new ArrayList<>();
		}
		iterStr(digits, new StringBuilder(), 0);
		return res;
	}
	//最终输出结果的list
	List<String> res = new ArrayList<>();
	
	//递归函数
	void iterStr(String str, StringBuilder letter, int index) {
		//递归的终止条件,注意这里的终止条件看上去跟动态演示图有些不同,主要是做了点优化
		//动态图中是每次截取字符串的一部分,"234",变成"23",再变成"3",最后变成"",这样性能不佳
		//而用index记录每次遍历到字符串的位置,这样性能更好
		if(index == str.length()) {
			res.add(letter.toString());
			return;
		}
		//获取index位置的字符,假设输入的字符是"234"
		//第一次递归时index为0所以c=2,第二次index为1所以c=3,第三次c=4
		//subString每次都会生成新的字符串,而index则是取当前的一个字符,所以效率更高一点
		char c = str.charAt(index);
		//map_string的下表是从0开始一直到9, c-'0'就可以取到相对的数组下标位置
		//比如c=2时候,2-'0',获取下标为2,letter_map[2]就是"abc"
		int pos = c - '0';
		String map_string = letter_map[pos];
		//遍历字符串,比如第一次得到的是2,页就是遍历"abc"
		for(int i=0;i<map_string.length();i++) {
			//调用下一层递归,用文字很难描述,请配合动态图理解
            letter.append(map_string.charAt(i));
            //如果是String类型做拼接效率会比较低
			//iterStr(str, letter+map_string.charAt(i), index+1);
            iterStr(str, letter, index+1);
            letter.deleteCharAt(letter.length()-1);
		}
	}
}


1768. 交替合并字符串

image-20220907143340720

class Solution {
    public String mergeAlternately(String word1, String word2) {
        String out = "";
        int max = word1.length() > word2.length() ? word1.length() : word2.length();
        for(int i = 0;i<max ;i++){
            if(i<word1.length()){
                out = out + word1.substring(i,i+1);
            }
            if(i<word2.length()){
                out = out + word2.substring(i,i+1);
            }
        }
        return out;
    }
}

双指针

class Solution {
    public String mergeAlternately(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        char[] res = new char[len1 + len2];
        int index = 0;
        for (int i = 0; i < len1 || i < len2; i++) {
            if (i < len1) {
                res[index++] = word1.charAt(i);
            }
            if (i < len2) {
                res[index++] = word2.charAt(i);
            }
        }
        return new String(res);
    }
}

笔试题:交替合并字符串2

image-20220907143739744

class Solution {
    public String mergeAlternately(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        char[] res = new char[Math.max(len1,len2)*2];
        int index = 0;
        for (int i = 0; i < Math.max(len1,len2); i++) {
            if (len1 == len2) {
                res[index++] = word1.charAt(i);
                res[index++] = word2.charAt(i);
            }
            if (len1 > len2) {
                int rest =  len1 % len2;
                int c = len1 /len2 -1;
                String temp = word2;
                while(c>0){
                    word2 = word2 + word2;
                    c--;
                }
                word2 = word2 + temp.substring(0, rest);
                res[index++] = word1.charAt(i);
                res[index++] = word2.charAt(i);
            }
            if (len1 < len2) {
                int rest =  len2 % len1;
                int c = len2 /len1 -1;
                String temp = word1;
                while(c>0){
                    word1 = word1 + word1;
                    c--;
                }
                word1 = word1 + temp.substring(0, rest);
                res[index++] = word1.charAt(i);
                res[index++] = word2.charAt(i);
            }
        }
        return new String(res);
    }
}

递归

669. 修剪二叉搜索树

image-20220910102013106

递归

由于被修剪的是二叉搜索树,因此修剪过程必然能够顺利进行。

容易想到使用原函数作为递归函数:

  • 若 root.val 小于边界值 low,则 root 的左子树必然均小于边界值,我们递归处理 root.right 即可;

  • 若 root.val 大于边界值 high,则 root 的右子树必然均大于边界值,我们递归处理 root.left 即可;

  • 若 root.val 符合要求,则 root 可被保留,递归处理其左右节点并重新赋值即可。

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if (root == null) {
            return null;
        }
        if (root.val < low) {
            return trimBST(root.right, low, high);
        }else if (root.val>high) {
            return trimBST(root.left, low, high);
        }else {
            root.left = trimBST(root.left, low, high);
            root.right = trimBST(root.right, low, high);
            return root;
        }
    }
}

迭代

自然能够使用「迭代」进行求解:起始先从给定的 root 进行出发,找到第一个满足值符合 [low, high][low,high] 范围的节点,该节点为最后要返回的根节点 ans。

随后考虑如何修剪 ans 的左右节点:当根节点符合 [low, high][low,high] 要求时,修剪左右节点过程中仅需考虑一边的边界值即可。即对于 ans.left 只需考虑将值小于 low 的节点去掉(因为二叉搜索树的特性,ans 满足不大于 high 要求,则其左节点必然满足);同理 ans.right 只需要考虑将大于 high 的节点去掉即可。

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        while (root != null && (root.val < low || root.val > high)) root = root.val < low ? root.right : root.left;
        TreeNode ans = root;
        while (root != null) {
            while (root.left != null && root.left.val < low) root.left = root.left.right;
            root = root.left;
        }
        root = ans;
        while (root != null) {
            while (root.right != null && root.right.val > high) root.right = root.right.left;
            root = root.right;
        }
        return 
        ans;
    }
}

11.模拟题

915. 分割数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0yMdtbh-1667540758266)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20221024204850231.png)]

思路: 先通过一次遍历从后往前统计出所有后缀的最小值min;然后第二次遍历从前往后统计每个前缀的最大值,找到一个符合条件的分割点就是答案

class Solution {
    public int partitionDisjoint(int[] nums) {
        int n = nums.length;
        int[] min = new int[n + 10];
        min[n-1] = nums[n-1];
        for (int i = n - 2; i >= 0; i--) {
            min[i] = Math.min(min[i + 1], nums[i]);
        }
        for (int i = 0, max = 0; i < n - 1; i++) {
            max = Math.max(max, nums[i]);
            if (max <= min[i + 1]) {
                return i + 1;
            }

        }
        return -1;


    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a11quOhF-1667540758267)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20221024211454904.png)]

class Solution {
    public int partitionDisjoint(int[] nums) {
        int n = nums.length;
        int leftMax = nums[0], leftPos = 0, curMax = nums[0];
        for (int i = 1; i < n-1; i++) {
            curMax = Math.max(curMax, nums[i]);
            if (nums[i] < leftMax) {
                leftMax = curMax;
                leftPos = i;
            }
        }
        return leftPos + 1;
    }
}

递归

24. 两两交换链表中的节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OWRFSh21-1667540758267)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20221025181116176.png)]

递归
class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode temp = head.next;
        head.next = swapPairs(temp.next);
        temp.next = head;
        return temp;
    }
}
迭代
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode temp = dummy;
        while (temp.next != null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            temp = node1;
        }
        return dummy.next;
    }
}

其他

剑指 Offer 39. 数组中出现次数超过一半的数字

image-20220817080347516

摩尔投票法: 核心理念为 票数正负抵消 。此方法时间和空间复杂度分别为 O(N) 和 O(1) ,为本题的最佳解法。

class Solution {
    public int majorityElement(int[] nums) {
        int count = 0 , target = 0;
        for(int num : nums){
            if(count == 0) target = num;
            count += target == num ? 1 : -1;
        }
        return target;
       
    }
}
class Solution {
    public int majorityElement(int[] nums) {
        int len = nums.length;
        int target = 0,count = 0;
        for(int i = 0; i<len ; i++){
            if(count == 0){
                target = nums[i];
                count = 1;
            } else if(nums[i] == target) {
                count++;
            } else {
                count--;
            }
        }
        return target;
       
    }
}

1675. 数组的最小偏移量

31. 下一个排列

思路:

为了更好理解,我们结合样例来分析,假设样例为 [1,3,5,4,1]:

从后往前找,找到第一个下降的位置,记为 k。注意k 以后的位置是降序的。 在样例中就是找到 3

从 k 往后找,找到最小的比 k 要大的数。 找到 4

将两者交换。注意此时 k 以后的位置仍然是降序的。

直接将 k 以后的部分翻转(变为升序)。

注意:如果在步骤 1 中找到头部还没找到,说明该序列已经是字典序最大的排列。按照题意,我们要将数组重新排列成最小的排列。

image-20220905113828775

class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        int k = n-1;
        //当k大于0,且从后往前一直为升序,则k减一
        while (k - 1 >= 0 && nums[k-1] >= nums[k]) {
            k--;
        }
        //如果k等于0。则翻转整个数组
        if (k == 0) {
            reverse(nums, 0, n - 1);
        }else {
            
            int u = k;
            //判断是否nums[u+1] > nums[k-1],大于则u++,直到找到一个小于等于的,作为交换
            while (u+1<n && nums[u+1] > nums[k-1]) {
                u++;
            }
            //交换索引k-1和u的位置的数
            swap(nums, k - 1, u);
            //将索引k后面的数组做升序翻转
            reverse(nums, k, n - 1);
        }
    }
    //创建翻转方法,利用双指针遍历翻转成为升序数组
    void reverse(int[] nums, int a, int b) {
        int l = a,r = b;
        while (l<r) {
            swap(nums,l++,r--);
        }
    }
    //创建方法交换两个数
    void swap(int[] nums, int a, int b) {
        int c = nums[a];
        nums[a] = nums[b];
        nums[b] = c;
    }
    
}

2396. 严格回文的数字

剑指 Offer II 096. 字符串交织

2196. 根据描述创建二叉树

1558. 得到目标数组的最少函数调用次数

850. 矩形面积 II

image-20220916234020016

class Solution {
    int MOD = (int)1e9+7;
    public int rectangleArea(int[][] rs) {
        List<Integer> list = new ArrayList<>();
        for (int[] info : rs) {
            list.add(info[0]); list.add(info[2]);
        }
        Collections.sort(list);
        long ans = 0;
        for (int i = 1; i < list.size(); i++) {
            int a = list.get(i - 1), b = list.get(i), len = b - a;
            if (len == 0) continue;
            List<int[]> lines = new ArrayList<>();
            for (int[] info : rs) {
                if (info[0] <= a && b <= info[2]) lines.add(new int[]{info[1], info[3]});
            }
            Collections.sort(lines, (l1, l2)->{
                return l1[0] != l2[0] ? l1[0] - l2[0] : l1[1] - l2[1];
            });
            long tot = 0, l = -1, r = -1;
            for (int[] cur : lines) {
                if (cur[0] > r) {
                    tot += r - l;
                    l = cur[0]; r = cur[1];
                } else if (cur[1] > r) {
                    r = cur[1];
                }
            }
            tot += r - l;
            ans += tot * len;
            ans %= MOD;
        }
        return (int) ans;
    }
}

448. 找到所有数组中消失的数字

image-20220922230039559

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        int n = nums.length;
        for (int num : nums) {
            int x = (num - 1) % n;
            nums[x] += n;
        }
        List<Integer> res = new ArrayList<Integer>();
        for (int i = 0; i < n; i++) {
            if (nums[i] <= n) {
                res.add(i + 1);
            }
        }
        return res;
    }
}

剑指 Offer 67. 把字符串转换成整数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCLwCjpl-1667540758269)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20220927100803797.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBXHjrgI-1667540758270)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20220927100841664.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xOgZ3FI5-1667540758270)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20220927100854857.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0b4ZILw8-1667540758270)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20220927100925979.png)]

class Solution {
    public int strToInt(String str) {
        int res = 0, bndry = Integer.MAX_VALUE / 10;
        int i = 0, sign = 1, length = str.length();
        if(length == 0) return 0;
        while(str.charAt(i) == ' ')
            if(++i == length) return 0;
        if(str.charAt(i) == '-') sign = -1;
        if(str.charAt(i) == '-' || str.charAt(i) == '+') i++;
        for(int j = i; j < length; j++) {
            if(str.charAt(j) < '0' || str.charAt(j) > '9') break;
            if(res > bndry || res == bndry && str.charAt(j) > '7')
                return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            res = res * 10 + (str.charAt(j) - '0');
        }
        return sign * res;
    }
}

67. 二进制求和

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qCZWOe3f-1667540758270)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20220927102833005.png)]

思路
整体思路是将两个字符串较短的用 00 补齐,使得两个字符串长度一致,然后从末尾进行遍历计算,得到最终结果。

本题解中大致思路与上述一致,但由于字符串操作原因,不确定最后的结果是否会多出一位进位,所以会有 2 种处理方式:

  • 第一种,在进行计算时直接拼接字符串,会得到一个反向字符,需要最后再进行翻转
  • 第二种,按照位置给结果字符赋值,最后如果有进位,则在前方进行字符串拼接添加进位
class Solution {
    public String addBinary(String a, String b) {
        StringBuilder sb = new StringBuilder();
        int ca = 0;
        for (int i = a.length() - 1, j = b.length() - 1; i >= 0 || j >= 0; i--, j--) {
            int sum = ca;
            sum += i >= 0 ? a.charAt(i) - '0' : 0;
            sum += j >= 0 ? b.charAt(j) - '0' : 0;
            sb.append(sum % 2);
            ca = sum / 2;
        }
        sb.append(ca == 1 ? ca : "");
        return sb.reverse().toString();
    }
}

29. 两数相除

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dA6QZ7ub-1667540758271)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20221027190556041.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aClwAfYk-1667540758271)(C:/Users/Administrator/Desktop/%E9%87%8D%E8%A6%81%E6%96%87%E4%BB%B6/assets/image-20221027190618501.png)]


笔试题

1.删除K个重复元素

image-20220907154701777

image-20220907154920037


2.寻找所有的公共祖先

image-20220907155617779

3.整数的二进制回文数

image-20220907165954887

public class Test{
    public static void main(String[] args) {
        int x = 9;
        System.out.println(ispalindromic(x));
    }
    static boolean ispalindromic(int n)
    {
        int count = 0;
        int m1 = n,m2 =0;
        for (count = 0; m1 != 0; count++) {
            m1 >>= 1;
        }
        m1 = n;
        for (; count > 0; count--){
            m2 <<= 1; m2 += (m1 & 1); m1 >>= 1; 
        }
        if (m2 == n){
            return true;
        }
        else {
            return false;
        }
    }
}

有效电话簿

红包分割

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcGSz2vl-1667540758273)(https://gitee.com/stars_shine/cloud-image/raw/master/image/20220923154318.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhQohedh-1667540758273)(https://gitee.com/stars_shine/cloud-image/raw/master/image/20220923154319.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mctJHk7u-1667540758273)(https://gitee.com/stars_shine/cloud-image/raw/master/image/20220923154320.png)]

public class T2 {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int[] arr = Stream.of(sc.nextLine().split(" ")).mapToInt(Integer::valueOf).toArray();
        int n = arr.length;
        // pre[i]:前缀和,包括i
        long[] pre = new long[n];
        pre[0] = arr[0];

        for (int i = 1; i < n; i++) {
            pre[i] = pre[i - 1] + arr[i];
        }

        int ans = 0;

        // i:分割线1 j:分割线2
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                long a = pre[i];
                long b = pre[j] - pre[i];
                long c = pre[n - 1] - pre[j];
                if (a <= b && b <= c) ans++;
            }
        }

        System.out.println(ans);
    }

}

区间题目

这里用到了Arrays.sort()方法。

Arrays.sort(intervals,(a,b)->a[0]-b[0]);

这个方法乍一看有点懵。后面查了 一些博客根据自己的总结如下

Arrays.sort方法的完整应该是这样

 Arrays.sort(intervals, new Comparator<int[]>(){
    @Override
    public int compare(int[] a, int[] b) {
    return a[0]-b[0];//如果这个值大于0,则是升序
}

然后后面的匿名类可以写成Lambda表达式,可以省略函数名,返回值类型和return 关键字,只要给定参数(a,b),直接得到一个表达式的结果

Arrays.sort(intervals,  (a, b)->a[0]-b[0]);

力扣 252. 会议室

给定一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,请你判断一个人是否能够参加这里面的全部会议。

示例 1::
输入: intervals = [[0,30],[5,10],[15,20]]
输出: false
解释: 存在重叠区间,一个人在同一时刻只能参加一个会议。

示例 2::
输入: intervals = [[7,10],[2,4]]
输出: true
解释: 不存在重叠区间。

思路分析

对Comparetor.compare(o1, o2)方法的返回值,如果返回的值小于零,则不交换两个o1和o2的位置;如果返回的值大于零,则交换o1和o2的位置。

因为一个人在同一时刻只能参加一个会议,因此题目实质是判断是否存在重叠区间,这个简单,将区间按照会议开始时间进行排序,然后遍历一遍判断即可。

Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);

  1. 排序规则:如果传递的参数是第一个是o1,第二个是o2,比较的时候也是用o1-o2进行比较,那么就是升序;如果比较的时候是用反过来o2-o1进行比较,那么就是降序
import java.util.Arrays;

class Solution {
    public boolean canAttentionMeetings(int[][] intervals) {
        // 将区间按照会议开始实现升序排序
        Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
        // 遍历会议,如果下一个会议在前一个会议结束之前就开始了,返回 false。
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] < intervals[i - 1][1]) {
                return false;
            }
        }
        return true;
    }
}

56. 合并区间

image-20220920225928005

思路分析

和上一题一样,首先对区间按照起始端点进行升序排序,然后逐个判断当前区间是否与前一个区间重叠,如果不重叠的话将当前区间直接加入结果集,反之如果重叠的话,就将当前区间与前一个区间进行合并。

class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
        int[][] res = new int[intervals.length][2];
        int idx =-1;
        for (int[] interval : intervals) {
            //如果结果数组为空,或者当前区间的起始位置>结果数组最后区间的中止位置则表示重叠
            //则不合并,将当前区间加入结果数组中
            if (idx == -1 || interval[0] > res[index][1]) {
                res[++index] = interval;
            }else {
                //重叠,则将当前区间合并到结果数组的最后区间
                res[idx][1] = Math.max(res[idx][1],intervals[1]);
            }
        }
        return Arrays.asList(res, idx + 1);
    }
}

57. 插入区间

image-20220921111357422

思路分析

本题中的区间已经按照起始端点升序排列,因此我们直接遍历区间列表,寻找新区间的插入位置即可。具体步骤如下:

  1. 首先将新区间左边且相离的区间加入结果集(遍历时,如果当前区间的结束位置小于新区间的开始位置,说明当前区间在新区间的左边且相离);
  2. 接着判断当前区间是否与新区间重叠,重叠的话就进行合并,直到遍历到当前区间在新区间的右边且相离,将最终合并后的新区间加入结果集;
  3. 最后将新区间右边且相离的区间加入结果集。
class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        int[][] res = new int[intervals.length + 1][2];
        int index = 0;
        //遍历区间列表
        //首先将新区间左边且相离的区间加入结果数组
        int i = 0;
        while (i < intervals.length && intervals[i][1] < newInterval[0]) {
            res[index++] = intervals[i++];
        }
        // 接着判断当前区间是否与新区间重叠,重叠的话就进行合并,直到遍历到当前区间在新区间的右边且相离,
        // 将最终合并后的新区间加入结果集
        while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
            newInterval[0] = Math.min(intervals[i][0], newInterval[0]);
            newInterval[1] = Math.max(intervals[i][1], newInterval[1]);
            i++;
        }
        res[index++] = newInterval;
        // 最后将新区间右边且相离的区间加入结果集
        while (i < intervals.length) {
            res[index++] = intervals[i++];
        }
        return Arrays.copyOf(res, index);
    }
}

228. 汇总区间

image-20220921113226485

image-20220921113656225

class Solution {
    public List<String> summaryRanges(int[] nums) {
        List<String> res = new ArrayList<>();
        int i = 0;
        int n = nums.length;
        while (i < n) {
            int low = i;
            i++;
            while (i < n && nums[i] == nums[i - 1] + 1) {
                i++;
            }
            int high = i-1;
            StringBuffer sb = new StringBuffer(Integer.toString(nums[low]));
            if (low < high) {
                sb.append("->").append(Integer.toString(nums[high]));
            }
            res.add(sb.toString());
        }
        return res;
    }
}

1288. 删除被覆盖区间

image-20220921114851788

3.整数的二进制回文数

[外链图片转存中…(img-LH3bh7Pt-1667540758272)]

public class Test{
    public static void main(String[] args) {
        int x = 9;
        System.out.println(ispalindromic(x));
    }
    static boolean ispalindromic(int n)
    {
        int count = 0;
        int m1 = n,m2 =0;
        for (count = 0; m1 != 0; count++) {
            m1 >>= 1;
        }
        m1 = n;
        for (; count > 0; count--){
            m2 <<= 1; m2 += (m1 & 1); m1 >>= 1; 
        }
        if (m2 == n){
            return true;
        }
        else {
            return false;
        }
    }
}

有效电话簿

红包分割

[外链图片转存中…(img-WcGSz2vl-1667540758273)]

[外链图片转存中…(img-qhQohedh-1667540758273)]

[外链图片转存中…(img-mctJHk7u-1667540758273)]

public class T2 {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int[] arr = Stream.of(sc.nextLine().split(" ")).mapToInt(Integer::valueOf).toArray();
        int n = arr.length;
        // pre[i]:前缀和,包括i
        long[] pre = new long[n];
        pre[0] = arr[0];

        for (int i = 1; i < n; i++) {
            pre[i] = pre[i - 1] + arr[i];
        }

        int ans = 0;

        // i:分割线1 j:分割线2
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                long a = pre[i];
                long b = pre[j] - pre[i];
                long c = pre[n - 1] - pre[j];
                if (a <= b && b <= c) ans++;
            }
        }

        System.out.println(ans);
    }

}

区间题目

这里用到了Arrays.sort()方法。

Arrays.sort(intervals,(a,b)->a[0]-b[0]);

这个方法乍一看有点懵。后面查了 一些博客根据自己的总结如下

Arrays.sort方法的完整应该是这样

 Arrays.sort(intervals, new Comparator<int[]>(){
    @Override
    public int compare(int[] a, int[] b) {
    return a[0]-b[0];//如果这个值大于0,则是升序
}

然后后面的匿名类可以写成Lambda表达式,可以省略函数名,返回值类型和return 关键字,只要给定参数(a,b),直接得到一个表达式的结果

Arrays.sort(intervals,  (a, b)->a[0]-b[0]);

力扣 252. 会议室

给定一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,请你判断一个人是否能够参加这里面的全部会议。

示例 1::
输入: intervals = [[0,30],[5,10],[15,20]]
输出: false
解释: 存在重叠区间,一个人在同一时刻只能参加一个会议。

示例 2::
输入: intervals = [[7,10],[2,4]]
输出: true
解释: 不存在重叠区间。

思路分析

对Comparetor.compare(o1, o2)方法的返回值,如果返回的值小于零,则不交换两个o1和o2的位置;如果返回的值大于零,则交换o1和o2的位置。

因为一个人在同一时刻只能参加一个会议,因此题目实质是判断是否存在重叠区间,这个简单,将区间按照会议开始时间进行排序,然后遍历一遍判断即可。

Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);

  1. 排序规则:如果传递的参数是第一个是o1,第二个是o2,比较的时候也是用o1-o2进行比较,那么就是升序;如果比较的时候是用反过来o2-o1进行比较,那么就是降序
import java.util.Arrays;

class Solution {
    public boolean canAttentionMeetings(int[][] intervals) {
        // 将区间按照会议开始实现升序排序
        Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
        // 遍历会议,如果下一个会议在前一个会议结束之前就开始了,返回 false。
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] < intervals[i - 1][1]) {
                return false;
            }
        }
        return true;
    }
}

56. 合并区间

[外链图片转存中…(img-CbcGt0x6-1667540758274)]

思路分析

和上一题一样,首先对区间按照起始端点进行升序排序,然后逐个判断当前区间是否与前一个区间重叠,如果不重叠的话将当前区间直接加入结果集,反之如果重叠的话,就将当前区间与前一个区间进行合并。

class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
        int[][] res = new int[intervals.length][2];
        int idx =-1;
        for (int[] interval : intervals) {
            //如果结果数组为空,或者当前区间的起始位置>结果数组最后区间的中止位置则表示重叠
            //则不合并,将当前区间加入结果数组中
            if (idx == -1 || interval[0] > res[index][1]) {
                res[++index] = interval;
            }else {
                //重叠,则将当前区间合并到结果数组的最后区间
                res[idx][1] = Math.max(res[idx][1],intervals[1]);
            }
        }
        return Arrays.asList(res, idx + 1);
    }
}

57. 插入区间

[外链图片转存中…(img-3qi9YuI0-1667540758274)]

思路分析

本题中的区间已经按照起始端点升序排列,因此我们直接遍历区间列表,寻找新区间的插入位置即可。具体步骤如下:

  1. 首先将新区间左边且相离的区间加入结果集(遍历时,如果当前区间的结束位置小于新区间的开始位置,说明当前区间在新区间的左边且相离);
  2. 接着判断当前区间是否与新区间重叠,重叠的话就进行合并,直到遍历到当前区间在新区间的右边且相离,将最终合并后的新区间加入结果集;
  3. 最后将新区间右边且相离的区间加入结果集。
class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        int[][] res = new int[intervals.length + 1][2];
        int index = 0;
        //遍历区间列表
        //首先将新区间左边且相离的区间加入结果数组
        int i = 0;
        while (i < intervals.length && intervals[i][1] < newInterval[0]) {
            res[index++] = intervals[i++];
        }
        // 接着判断当前区间是否与新区间重叠,重叠的话就进行合并,直到遍历到当前区间在新区间的右边且相离,
        // 将最终合并后的新区间加入结果集
        while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
            newInterval[0] = Math.min(intervals[i][0], newInterval[0]);
            newInterval[1] = Math.max(intervals[i][1], newInterval[1]);
            i++;
        }
        res[index++] = newInterval;
        // 最后将新区间右边且相离的区间加入结果集
        while (i < intervals.length) {
            res[index++] = intervals[i++];
        }
        return Arrays.copyOf(res, index);
    }
}

228. 汇总区间

[外链图片转存中…(img-UWko85Yn-1667540758274)]

[外链图片转存中…(img-ezg8Mkyb-1667540758275)]

class Solution {
    public List<String> summaryRanges(int[] nums) {
        List<String> res = new ArrayList<>();
        int i = 0;
        int n = nums.length;
        while (i < n) {
            int low = i;
            i++;
            while (i < n && nums[i] == nums[i - 1] + 1) {
                i++;
            }
            int high = i-1;
            StringBuffer sb = new StringBuffer(Integer.toString(nums[low]));
            if (low < high) {
                sb.append("->").append(Integer.toString(nums[high]));
            }
            res.add(sb.toString());
        }
        return res;
    }
}

1288. 删除被覆盖区间

[外链图片转存中…(img-2kBZ3asC-1667540758275)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值