CodTop 热题100

目录

0、单例模式

1、leetcode3: 最长无重复子串

关联问题_leetcode395: 至少有 K 个重复字符的最长子串

2、leetcode206: 反转链表

关联问题_leetcode25: K个一组翻转链表

关联问题_leetcode92: 反转链表 II 

 3、leetcode146: LRU 缓存

 4、leetcode215: 数组中的第K个最大元素

5、leetcode25: K个一组翻转链表

6、leetcode15: 三数之和

关联问题_leetcode16: 最接近的三数之和

8、手撕快排和堆排

9、leetcode21: 合并两个有序链表

关联问题_合并两个升序链表并去重1

关联问题_合并两个升序链表并去重2

10、leetcode1: 两数之和

关联问题_leetcode39: 组合总和

关联问题_leetcode40: 组合总和 II

11、leetcode5: 最长回文子串

关联问题_leetcode647:回文子串

关联问题_leetcode516:最长回文子序列

12、leetcode102: 二叉树的层序遍历

队列解法

递归解法

关联问题_leetcode103:二叉树的Z字型遍历

关联问题_leetcode124:二叉树中的最大路径和

13、leetcode33:搜索旋转排序数组

关联问题_leetcode153:寻找旋转排序数组中的最小值

关联问题_leetcode154: 寻找旋转排序数组中的最小值 II

14、leetcode200: 岛屿数量

关联问题_leetcode695:岛屿的最大面积

关联问题_leetcode463: 岛屿的周长


0、单例模式

双重校验锁的实例必须加volatile修饰。这是因为java在初始化一个对象时分为三步:

  1. 分配内存空间。
  2. 初始化实例对象。
  3. 将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

  1. 分配内存空间。
  2. 将内存空间的地址赋值给对应的引用。
  3. 初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。关键字: volatile详解 | Java 全栈知识体系

/** 
 * 饿汉模式:类加载时就初始化实例
 * 这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。 
 * 缺点:在类装载的时候就完成实例化,没有达到lazy loading的效果,
 *      如果从始至终都未使用过这个实例,则会造成内存的浪费
 */
public class SingleInstance{
    // 构造函数必须是 private,不允许外部访问
    private SingleInstance(){}
    
    private static final SingleInstance instance = new SingleInstance();

    public static SingleInstance getInstance(){
        return this.instance;
    }
}

/** 
 * 懒汉模式:需要用的时候再创建
 */
public class SingleInstance{
    // 构造函数必须是 private,不允许外部访问
    private SingleInstance(){}
    
    private static SingleInstance instance;

    // 线程安全的同步写法,效率低
    public static synchronized SingleInstance getInstance(){
        if(instance == null) {
            instance = new SingleInstance();
        }
        return instance;
    }
}

/** 
 * 双重校验锁模式:相比于懒汉线程安全模式,此方式效率更高,初始化完成后获取实例无需加锁
 */
public class SingleInstance{
    // 构造函数必须是 private,不允许外部访问
    private SingleInstance(){}
    
    private volatile static SingleInstance instance;

    public static SingleInstance getInstance(){
        // 第一次校验为空
        if(instance == null) {
            // 加锁
            synchronized(SingleInstance.class) {
                // 第二次校验为空
                if(instance == null) {
                    instance = new SingleInstance();   
                }     
            }
        }
        return instance;
    }
}

1、leetcode3: 最长无重复子串

题目描述: 给定一个字符串,求其中最长的无重复子串。

例1: “abcab” -> 3

例1: “abba” -> 2

public static int lengthOfLongestSubstring(String s) {
    if(s == null || s.length() == 0) return 0;

    Map<Character, Integer> map = new HashMap<>();
    int max = 0, left = 0;

    for(int i = 0; i < s.length(); i++) {
        char ch = s.charAt(i);
        if(map.containsKey(ch)) {
            // 加max防止"abba" 这种case,left值反转
            left = Math.max(left, map.get(ch) + 1);
        }
        map.put(ch, i);
        max = Math.max(max, i-left+1);
    }
    return max;
}

关联问题_leetcode395: 至少有 K 个重复字符的最长子串

题目描述:给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。如果不存在这样的子字符串,则返回 0。

public int longestSubstring(String s, int k) {
    if(s == null || s.length() == 0) return 0;
    
    // 1、统计每个字符出现次数
    Map<Character, Integer> timesMap = new HashMap<>();
    for(int i = 0; i<s.length(); i++) {
        char ch = s.charAt(i);
        int times = timesMap.getOrDefault(ch, 0);
        timesMap.put(ch, times+1);
    }

    // 2、遍历map,如果字符出现次数<k,去掉此字符,重新递归获取最大长度.
    for(Character ch : timesMap.keySet()) {
        int times = timesMap.get(ch);
        if(times < k) {
            String[] strs = s.split(String.valueOf(ch));
            int max = 0;
            for(String str : strs) {
                int len = longestSubstring(str, k);
                max = Math.max(max, len);
            }
            return max;
        }
    }
    return s.length();   
}

2、leetcode206: 反转链表

// 递归
public static ListNode reverse(ListNode head){
    if(head == null || head.next == null) return head;
    ListNode node = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return node;
}

// 迭代
public static ListNode reverse(ListNode head){
    if(head == null || head.next == null) return head;
    
    ListNode pre = null, next;
    while(head != null) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

关联问题_leetcode25: K个一组翻转链表

题目描述:给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

例如:输入:head = [1,2,3,4,5], k = 3 输出:[3,2,1,4,5]

public ListNode reverseKGroup(ListNode head, int k) {
    if(head == null || head.next == null || k<=1) return head;
    
    ListNode res = new ListNode(-1), tmp = res, current = head, start, next;
    tmp.next = head;

    while(current != null) {
        start = current;
        for(int i=0; i<k-1 && current != null; i++) {
            current=current.next;
        }
        if(current == null) return res.next;
        next = current.next;
        current.next = null;
        tmp.next = reverse(start);
        start.next = next;
        tmp = start;
        current = next;
    }
    return res.next;
}

public ListNode reverse(ListNode head) {
    if(head == null || head.next == null) return head;
    ListNode node = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return node;
}

关联问题_leetcode92: 反转链表 II 

题目描述:给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

例如:输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5]

public ListNode reverseII(ListNode head, int left, int right) {
    if(head == null 
        || head.next == null 
        || left < 0
        || right < 0
        || left >= right ) {
        return head;
    }

    ListNode res = new ListNode(-1), tmp = res, current = head, start, next;
    tmp.next = head;

    // 找到反转的起始位置
    for(int i=0; i<left-1 && current != null; i++) {
        tmp = tmp.next;
        current = current.next;
    }
    if(current == null) return head;
    start = current;

    // 找到反转的结束位置. 往后遍历 right-left步
    for(int i=0; i<right-left && current != null; i++) {
        current = current.next;
    }
    if(current == null) return head;
    next = current.next;
    current.next = null;

    // 反转 & 拼接
    tmp.next = reverse(start);
    start.next = next;
    return res.next;
}

public ListNode reverse(ListNode head){
    if(head == null || head.next == null) return head;

    ListNode node = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return node;
}

 3、leetcode146: LRU 缓存

题目描述:请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行

例如:

LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

解题思路:LRU底层是通过双端链表实现的。双端连表节点存储了key和value,以及指向上一个节点或者下一个节点的指针。插入、更新或者查询的节点都移动到队列头部,检查连表长度,末尾淘汰最近最久未使用的节点。因为题目要求get和put需要在O(1) 复杂度完成,可以通过map存储每个key跟节点的映射。

class LRUCache {
    private int capacity;
    private Node head;
    private Node tail;
    private Map<Integer, Node> cache;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new HashMap<>(capacity);

        this.head = new Node();
        this.tail = new Node();
        this.head.next = tail;
        this.tail.pre = this.head;
    }
    
    public int get(int key) {
        if(!cache.containsKey(key)) return -1;
        Node node = cache.get(key);
        moveToHead(node);
        return node.val;
    }
    
    public void put(int key, int value) {
        Node node = cache.getOrDefault(key, null);
        if(node != null) {
            node.val = value;
            moveToHead(node);
            return;
        }

        node = new Node(key, value);
        addToHead(node);
        cache.put(key, node);
        if(cache.size() > this.capacity) {
            Node deleteNode = deleteLast();
            cache.remove(deleteNode.key);
        }
    }

    public void moveToHead(Node node) {
        Node pre = node.pre;
        Node next = node.next;
        pre.next = next;
        next.pre = pre;

        addToHead(node);
    }

    public void addToHead(Node node) {
        Node next = head.next;

        head.next= node;
        node.next = next;
        next.pre = node;
        node.pre = head;
    }

    public Node deleteLast(){
        Node deleteNode = this.tail.pre;

        Node pre = deleteNode.pre;
        pre.next = this.tail;
        this.tail.pre = pre;
        return deleteNode;
    }

    /**
     * 双端链表节点
     */
    static class Node{
        private int key;
        private int val;
        private Node pre;
        private Node next;

        public Node(){}
        public Node(int key, int val){
            this.key = key;
            this.val = val;  
            this.pre = new Node();
            this.next = new Node();  
        }
    }
}

 4、leetcode215: 数组中的第K个最大元素

题目描述:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

例如:输入: [3,2,1,5,6,4], k = 2。 输出: 5

解题思路1:使用优先队列【底层是堆】。

解题思路2:使用堆【小顶堆,堆底层存的是最大的k个数,堆顶即为所求】。

// 优先队列
class Solution1 {
    public int findKthLargest(int[] nums, int k) {
        if(nums == null || nums.length < k) return -1;
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>(k, (o1,o2) -> {return o1-o2;});
        for(int i = 0;i<k; i++) {
            queue.add(nums[i]);
        }
        
        for(int i=k; i< nums.length; i++) {
            if(nums[i] > queue.peek()) {
                queue.poll();
                queue.add(nums[i]);
            }
        }
        return queue.peek();
    }
}

// 小顶堆
class Solution2 {
    public int findKthLargest(int[] nums, int k) {
        if(nums == null || nums.length < k) return -1;
        int[] heap = new int[k];
        for(int i = 0;i<k; i++) {
            heap[i] = nums[i];
        }

        for(int i=k; i>=0; i--) {
            adjust(heap, i, k);
        }
        
        for(int i=k; i< nums.length; i++) {
            if(nums[i] > heap[0]) {
                heap[0] = nums[i];
                adjust(heap, 0, k);
            }
        }
        return heap[0];
    }

    public void adjust(int[] nums, int i, int length) {
        int left = 2*i+1, right = 2*i+2, smallest = i;
        if(left < length && nums[left] < nums[smallest]) {
            smallest = left;
        }
        if(right < length && nums[right] < nums[smallest]) {
            smallest = right;
        }
        if(smallest == i) return;
        swap(nums, i, smallest);
        adjust(nums, smallest, length);
    }
    public void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

5、leetcode25: K个一组翻转链表

public ListNode reverseKGroup(ListNode head, int k) {
    if(head == null || head.next == null || k<= 1) {
        return head;
    }
    ListNode res = new ListNode(-1), tmp = res, current = head, start, next;
    res.next = head;
    while(current != null) {
        start = current;
        for(int i=0; i<k-1 && current != null; i++) {
            current = current.next;
        }
        if(current == null) return res.next;
        next = current.next;
        current.next = null;
        tmp.next = reverse(start);
        start.next = next;
        tmp = start;
        current = next;
    }
    return res.next;
}

public ListNode reverse(ListNode head){
    if(head == null || head.next == null) return head;
    ListNode node = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return node;
}

6、leetcode15: 三数之和

题目描述:给你一个整数数组 nums ,判断是否存在三元组:[nums[i],nums[j],nums[k]],满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

解题思路:

  1. 将数组排序
  2. 从下标0开始遍历
  3. 双指针循环遍历三个数累加之和是否=0
public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    if(nums == null || nums.length < 3) return res;

    // 排序    
    Arrays.sort(nums);

    for(int i=0; i < nums.length; i++) {
        // 不能有重复的,相同的跳过
        if(i>0 && nums[i] == nums[i-1]) continue;

        // 都是大于0的数,肯定不为0
        if(nums[i] > 0) break;

        int left = i+1, right = nums.length-1;
        while(left<right) {
            int sum = nums[i] + nums[left] + nums[right];
            if(sum == 0) {
                res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                // 不能有重复值
                while(left+1 < right && nums[left] == nums[left+1]) left++;
                left++;

                // 不能有重复值
                while(left < right-1 && nums[right] == nums[right-1]) right--;
                right--;
            }else if(sum > 0) {
                right--;
            }else {
                left++;
            }
        } 
    }

    return res;
}

关联问题_leetcode16: 最接近的三数之和

题目描述:给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在恰好一个解

解题思路:

  1. 将数组排序
  2. 从下标0开始遍历
  3. 每次记录sum与target 差值的绝对值
  4. 输出最小的差值对应的和
public int threeSumClosest(int[] nums, int target) {
    if(nums == null || nums.length < 3) return -1;

    Arrays.sort(nums);

    int minDiff = Integer.MAX_VALUE,  res = 0;
    for(int i=0; i < nums.length; i++) {
        if(i>0 && nums[i] == nums[i-1]) continue;

        int left = i+1, right = nums.length-1;
        while(left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            int diff = Math.abs(sum - target);
            minDiff = Math.min(minDiff, diff);
            res = minDiff == diff ? sum : res; 
            if(sum == target) return target;
            if(sum < target) {
                left++;
            }else{
                right--;
            }
        } 
    }
    return res;
}

7、leetcode53:  最大子数组和

题目描述:求最大子序列之和。

例如: [-2, 1, -3, 4, -1, 2, 1, -5, 4],  输出: 6 [4,-1,2,1]

解题思路:遍历累加数组中的元素,如果sum >= 0, 接着累加,否则sum = nums[i]

public static int getMaxSubSummary(int[] nums){
    if(nums == null || nums.length == 0) return -1;
    
    int res = Integer.MIN_VALUE, sum = 0;
    for(int i=0; i<nums.length; i++) {
        if(sum >= 0) {
            sum += nums[i];
        }else {
            sum = nums[i];
        }
        res = Math.max(res, sum);
    }
    return res;
}

8、手撕快排和堆排

快排:每次找一个基准点(partition),将这个基准点位置的值搞对,然后分左右接着排序。

public static void quickSort(int[] nums) {
    if(nums == null || nums.length <= 1) return
    
    doQuickSort(nums, 0, nums.length-1);
}

public static void doQuickSort(int[] nums, int left, int right) {
    if(left < right) {
        int partition = getPartition(nums, left, right);
        doQuickSort(nums, left, partition-1);
        doQuickSort(nums, partition+1, right);
    }
}

public static int getpartition(int[] nums, int _left, int _right) {
    int left = _left, right = _right;
    // 选取基准点
    int tmp = nums[left];

    if(left != right) {
        while(left < right) {
            // 从右往左遍历,找到 < 基准点的值替换到left
            while(left < right && nums[right] >= tmp) right--;
            nums[left] = nums[right];

            // 从左往右遍历,找到 > 基准点的值替换到right
            while(left < right && nums[left] <= tmp) left++;
            nums[right] = nums[left];
        }
        // 最后此下标即为基准点应该待的位置
        nums[left] = tmp;
    }
    // left下标排序完毕
    return left;
}

堆排:构建大顶堆,每次得到最大值,然后往数组后面交换,迭代即可

public static void heapSort(int[] nums) {
    if(nums == null || nums.length <= 1) return;

    // 建堆:大顶堆[最大值在最上面]
    for(int i=nums.length-1; i>=0; i++) {
        adjust(nums, i, nums.length);
    }

    // 遍历:每次把最大的值交换至队尾,剩余未排序部分继续调整
    for(int i = nums.length-1; i>=0; i++) {
        // 将本次获取最大值交换至i位置
        swap(nums, 0, i);
        // 继续从0开始,查找[0,i-1]之间最大的值
        adjust(nums, 0, i);
    }
}

public void adjust(int[] nums, int i, int length) {
    // 获取左子节点和右子节点
    int left = 2*i+1, right = 2*i+2, biggest = i;

    // 获取最大的值索引
    if(left < length && nums[left] < nums[biggest]) {
        biggest = left;
    }

    if(right < length && nums[right] < nums[biggest]) {
        biggest = right;
    }

    if(biggest == i) return;

    // 交换
    swap(nums, biggest, i);

    // 递归
    adjust(nums, biggest, length);
}

public void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

9、leetcode21: 合并两个有序链表

public static ListNode mergeListNode(ListNode p, ListNode q) {
    if(p == null) return q;
    if(q == null) return p;
    ListNode res = new ListNode(-1), tmp = res;
    while(p != null && q != null) {
        if(p.val < q.val) {
            tmp.next = p;
            p = p.next;
        }else{
            tmp.next = q;
            q = q.next;
        }
        tmp = tmp.next;
    }
    return res.next;
}

关联问题_合并两个升序链表并去重1

题目描述: 合并两个升序链表,并去重,只保留一个重复元素。

例如:[1, 2, 2, 3, 4, 5, 5] + [0,2] 输出: [0,1,2,3,4,5]

public static ListNode mergeListNodeWithoutOneDuplicate(ListNode p, ListNode q){
    ListNode res = new ListNode(-1), tmp = res;

    while(p != null && q != null) {
        // 去重
        while(p.next != null && p.val == p.next.val) {
            p = p.next;
        }

        while(q.next != null && q.val == q.next.val) {
            q = q.next;
        }
        
        if(p.val < q.val) {
            tmp.next = p;
            p = p.next;
        }else if (p.val > q.val) {
            tmp.next = q;
            q = q.next;
        }else {
            // 值相等,同时后移
            tmp.next = p;
            p = p.next;
            q = q.next;
        }
        tmp = tmp.next;
    }

    if(p != null) {
        while(p != null) {
            while(p.next != null && p.val == p.next.val) {
                p = p.next;
            }
            tmp.next = p;
            p = p.next;
            tmp = tmp.next;
        }
    }

    if(q != null) {
        while(q != null) {
            while(q.next != null && q.val == q.next.val) {
                q = q.next;
            }
            tmp.next = q;
            q = q.next;
            tmp = tmp.next;
        }
    }
    return res.next;
}

关联问题_合并两个升序链表并去重2

题目描述: 合并两个升序链表,并去重,只保留一个重复元素。

例如:[1, 2, 2, 3, 4, 5, 5] + [0,2] 输出: [0,1,3,4]

private static ListNode mergeListNodeWithoutDuplicate(ListNode p, ListNode q) {
    ListNode res = new ListNode(-1), tmp = res;
    while (p != null && q != null) {
        boolean pDuplicate = false, qDuplicate = false;
        while (p.next != null && p.val == p.next.val) {
            p = p.next;
            pDuplicate = true;
        }
        while (q.next != null && q.val == q.next.val) {
            q = q.next;
            qDuplicate = true;
        }
        if (p.val == q.val) {
            p = p.next;
            q = q.next;
            continue;
        }
        if (pDuplicate) {
            p = p.next;
            continue;
        }
        if (qDuplicate) {
            q = q.next;
            continue;
        }
        if (p.val < q.val) {
            tmp.next = p;
            p = p.next;
        }else{
            tmp.next = q;
            q = q.next;
        }
        tmp = tmp.next;
    }
    
    if(p != null) {
        tmp.next = deleteDuplicateNode(p);
    }
    if(q != null) {
        tmp.next = deleteDuplicateNode(q);    
    }
    return res.next;
}

public static ListNode deleteDuplicateNode(ListNode head) {
    ListNode res = new ListNode(-1), tmp = res;
    res.next = head;
    while(tmp.next != null) {
        if(tmp.next.next != null && tmp.next.val == tmp.next.next.val) {
            int val = tmp.next.val;
            while(tmp.next != null && tmp.next.val == val) {
                tmp.next = tmp.next.next;
            }
        }else {
            tmp = tmp.next;
        }
    }
    return res.next;
}

10、leetcode1: 两数之和

题目描述:返回两数之和为target 的数组下标

解法思路:

  1. 如果数组是有序连表,可以直接通过双指针的方式求解。
  2. 数组没规定一定有序且必须返回下标,所以不能对数组排序。只能通过map求解。
public static int[] getTwoSum(int[] nums, int target) {
    if(nums == null || nums.length <= 1) return new int[2];
    Map<Integer, Integer> map = new HashMap<>();
    for(int i = 0; i < nums.length; i++) {
        int num = target - nums[i];
        if(map.containsKey(num)) {
            return new int[]{map.get(num), i};
        }
        map.put(nums[i], i);
    }
    return new int[2];
}

关联问题_leetcode39: 组合总和

题目描述:给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

例:[1,1,2,1,4], target = 8,

输出:

        [1,1,1,1,1,1,1,1]
        [1,1,1,1,1,1,2]
        [1,1,1,1,2,2]
        [1,1,1,1,4]
        [1,1,2,2,2]
        [1,1,2,4]
        [2,2,2,2]
        [2,2,4]
        [4,4]

解题思路:

  1. 对数组排序
  2. dfs递归遍历。出口条件下标超限|累计和为target
  3. 递归的时候因为同一个元素可以重复使用,注意去重。且递归时传的idx参数需要注意
public List<List<Integer>> getDifferentCombine(int[] nums, int target) {
    List<List<Integer>> res = new ArrayList<>();
    if(nums == null || nums.length == 0) return res;
    
    // 先排序
    Arrays.sort(nums);
    dfs(nums, 0, target, new ArrayList<>(), res);
    return res;
}

public void dfs(int[] nums, int idx, int target, List<Integer> path,     List<List<Integer>> res) {
    // 递归退出条件1: 相加和为0
    if(target == 0) {
        res.add(new ArrayList<>(path));
        return;
    }

    // 递归退出条件2: 下标超限
    if(idx >= nums.length) {
        return;
    }

    for(int i = idx; i < nums.length; i++) {
        // 因为同一个数字可以重复使用,那么后面一样的直接去掉。
        if(i>0 && nums[i] == nums[i-1]) {
             continue;
        }

        int leftNum = target - nums[i];
        // 相加和为负数,跳过
        if(leftNum < 0) {
            continue;
        }

        // 此处因为一个位置的元素可以重复使用,所以传入的是 i 不是 i+1
        path.add(nums[i]);
        dfs(nums, i, leftNum, path, res);
        path.remove(path.size()-1);
    }
}

关联问题_leetcode40: 组合总和 II

题目描述:给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。 

例如:输入: [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]

解题思路:

  1. 对数组排序
  2. dfs递归遍历。出口条件下标超限|累计和为target
  3. 递归的时候因为同一个元素不可以重复使用,递归时传的idx参数需要注意+1
  4. 过滤时 i > idx 而不是 i > 0.
public List<List<Integer>> combinationSum2(int[] nums, int target) {
    List<List<Integer>> res = new ArrayList<>();
    if(nums == null || nums.length == 0) return res;

    Arrays.sort(nums);
    dfs(nums, 0, target, new ArrayList<>(), res);
    return res;
}    

public void dfs(int[] nums, int idx, int target, List<Integer> path,     List<List<Integer>> res) {
    if(target == 0) {
        res.add(new ArrayList<>(path));
        return;
    }

    if(idx >= nums.length) return;

    for(int i = idx; i<nums.length; i++) {
        /** 区别1: 此处取i>idx 是因为 [1,1,2] 如果idx=1,那么这个1还是要取,不能过滤 */
        if(i>idx && nums[i] == nums[i-1]) {
            continue;
        }

        if(target - nums[i] < 0) {
            continue;
        }

        /**区别2: 传入的下标+1 */
        path.add(nums[i]);
        dfs(nums, i+1, target - nums[i], new ArrayList<>(), res);
        path.remove(path.size()-1);
    }
}

11、leetcode5: 最长回文子串

题目描述:给你一个字符串 s,找到 s 中最长的 回文子串。

例如:s = "babad"  输出:"bab" 解释:"aba" 同样是符合题意的答案。

解题思路:中心扩散算法

public String longestPalindrome(String s) {
    if(s == null || s.length() == 0) return s;
    
    String res = "";
    for(int i =0; i< s.length(); i++) {
        String str1 = getSubStr(s, i, i); 
        String str2 = getSubStr(s, i, i+1);
        int maxLen = Math.max(res.length(), Math.max(str1.length(), str2.length()));
        if(maxLen == str1.length()) res = str1;
        if(maxLen == str2.length()) res = str2; 
    }
    return res;
}

public String getSubStr(String s, int i, int j) {
    while(i>=0 && j < s.length()) {
        if(s.charAt(i) == s.charAt(j)) {
            i--;
            j++;
        }else {
            break;
        }
    }
    return s.substring(i+1, j);
}

关联问题_leetcode647:回文子串

题目描述:给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。

例1: s = "abc" 输出:3 解释:三个回文子串: "a", "b", "c"

例2: s = "aaa" 输出:6 解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

解题思路:动态规划求解。定义dp[i] 表示以i-1处字符结尾的字符串的回文数。

  1. 默认值:dp[i] = dp[i-1] + 1;
  2. 遍历,0 <= j < i. 判断s.substring(j, i+1) 是否为回文串传,是的话dp[i]++;
public int countSubstrings(String s) {
    if(s == null || s.length() == 0) return 0;
    int[] dp = new int[s.length() + 1];

    for(int i = 0; i < s.length(); i++) {
        dp[i+1] = dp[i] + 1;
        for(int j=0; j<i; j++) {
            String substr = s.substring(j, i+1);
            if(isHuiWen(substr)) {
                dp[i+1]++;
            }
        }
    }
    return dp[s.length()];
}

public boolean isHuiWen(String s) {
    int l = 0, r = s.length()-1;
    while(l<r) {
        if(s.charAt(l) == s.charAt(r)) {
            l++;
            r--;
        }else {
            return false;
        }
    }
    return true;
}

关联问题_leetcode516:最长回文子序列

题目描述: 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

例1: s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。

例2: s = "cbbd" 输出:2 解释:一个可能的最长回文子序列为 "bb" 。


解题思路:动态规划。可以删除或者不删除。 定义 dp[i][j] 表示[i, j] 下标的子字符串最长回文子序列长度。

  1. 如果子字符串长度为1,即 i=j, 那dp[i][j] = 1;
  2. 如果子字符串长度为2,即 j=i+1, 如果字符相等,那dp[i][j] = 2, 否则dp[i][j] = 1;
  3. 如果子字符串长度为3,即 j=i+2, dp[i][j] = Max(dp[i][j-1], dp[i+1][j], dp[i+1][j-1] + ij字符相等 ? 2:0);
  4. 如果子字符串长度为4,即 j=i+3, dp[i][j] = Max(dp[i][j-1], dp[i+1][j], dp[i+1][j-1] + ij字符相等 ? 2:0);
  5. ................
  6. 最后返回dp[0][len-1];即为从0到 len-1 下标的子字符串最长回文序列长度。
public int longestPalindromeSubseq(String s) {
    if(s == null || s.length() == 0) return 0;
    int length = s.length();
    int[][] dp = new int[length][length];

    // 初始化1: 一个元素时,长度为1
    for(int i=0; i < s.length(); i++) {
        dp[i][i] = 1;
    }

    // 初始化2: 2个元素时,相等长度为2,否则为1
    for(int i=0; i < s.length()-1; i++) {
        dp[i][i+1] = s.charAt(i) == s.charAt(i+1) ? 2 : 1;
    }

    
    for(int len = 3; len <= length; len++) {
        for(int i = 0; i< length; i++) {
            // j 为结束的下标,最大值为length-1
            int j = i + len - 1; 
            if(j >= length) {
                break;
            }
            
            if(s.charAt(i) == s.charAt(j)) {
                dp[i][j] = max(dp[i+1][j], dp[i][j-1], dp[i+1][j-1] + 2);
            }else {
                dp[i][j] = max(dp[i+1][j], dp[i][j-1], dp[i+1][j-1]);
            }
        }
    }
    return dp[0][length-1];
}

public int max(int a, int b, int c) {
    return Math.max(a, Math.max(b, c));
}

12、leetcode102: 二叉树的层序遍历

队列解法

public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if(root == null) return res;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while(!queue.isEmpty()) {
        int len = queue.size();
        List<Integer> list = new ArrayList<>();
        for(int i = 0; i<len; i++) {
            TreeNode node = queue.poll();
            list.add(node.val);
            if(node.left != null) queue.add(node.left);
            if(node.right != null) queue.add(node.right);
        }
        res.add(list);
    }
    return res;
}

递归解法

public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if(root == null) return res;

    solveByRecursion(res, root, 1);
    return res;
}

public void solveByRecursion(List<List<Integer>> res, TreeNode root, int depth) {
    if(res.size() == depth-1) {
        res.add(new ArrayList<>());
    }
    
    res.get(depth-1).add(root.val);
    if(root.left != null) {
        solveByRecursion(res, root.left, depth+1);
    }

    if(root.right != null) {
        solveByRecursion(res, root.right, depth+1);
    }
}

关联问题_leetcode103:二叉树的Z字型遍历

解题思路1:同层序遍历,可以用两个栈实现。

解题思路1:同层序遍历,用一个队列实现。偶数层直接反转

// 两个栈
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if(root == null) return res;
    Stack<TreeNode> stack1 = new Stack<>();
    Stack<TreeNode> stack2 = new Stack<>();   
    stack1.add(root);
    while(!stack1.isEmpty() || !stack2.isEmpty()) {
        if(!stack1.isEmpty()) {
            int size = stack1.size();
            List<Integer> list = new ArrayList<>();
            for(int i = 0; i<size; i++) {
                TreeNode node = stack1.pop();
                list.add(node.val);
                if(node.left != null) stack2.add(node.left);
                if(node.right != null) stack2.add(node.right);
            }
            res.add(list);
        }
        
        if(!stack2.isEmpty()) {
            int size = stack2.size();
            List<Integer> list = new ArrayList<>();
            for(int i = 0; i<size; i++) {
                TreeNode node = stack2.pop();
                list.add(node.val);
                if(node.right != null) stack1.add(node.right);
                if(node.left != null) stack1.add(node.left);
            }
            res.add(list);
        }
    }
    return res;
}

// 一个队列
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if(root == null) return res;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    int depth = 0;
    while(!queue.isEmpty()) {
        int size = queue.size();
        List<Integer> tmp = new ArrayList<>();
        for(int i=0; i<size; i++) {
            TreeNode node = queue.poll();
            tmp.add(node.val);
            if(node.left != null) queue.add(node.left);
            if(node.right != null) queue.add(node.right);
        }
        depth++;
        // 偶数层直接反转
        if(depth % 2 == 0) {
            Collections.reverse(tmp);
        }
        
        res.add(tmp);
    }
    return res;
}

关联问题_leetcode124:二叉树中的最大路径和

题目描述: 路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。路径和 是路径中各节点值的总和。给定一个二叉树的根节点 root ,返回其 最大路径和,即所有路径上节点值之和的最大值。

例1: root = [1,2,3] 输出:6 解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

例2: root = [-10,9,20,null,null,15,7] 输出:42 解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42

例3: root=[2, -1] ,输出2,最优路径为根节点2。

解题思路:二叉树问题用递归求解

  1. 递归返回条件:节点为null,返回0
  2. 求左子树最大的路径和left。
  3. 求右子树最大的路径和right。
  4. 更新最大值为max(sum, left + right + root.val);
  5. 返回root.val + max(left, right);
private int maxSummary = Integer.MIN_VALUE;;
public int maxPathSum(TreeNode root) {
    if (root == null) {
        return 0;
    }

    dfsGetSum(root);
    return maxSummary;
}

private int dfsGetSum(TreeNode root) {
    if (root == null) {
        return 0;
    }

    // 此题的最大路径和不一定包含叶子结点,如[2,-1] 只算2根节点。
    int leftSum = Math.max(dfsGetSum(root.left), 0);
    int rightSum = Math.max(dfsGetSum(root.right), 0);
    maxSummary = Math.max(maxSummary, leftSum + rightSum + root.val);

    return Math.max(leftSum + root.val, rightSum + root.val);
}

13、leetcode33:搜索旋转排序数组

题目描述:整数数组 nums 按升序排列,数组中的值 互不相同 。在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

例1: nums = [4,5,6,7,0,1,2], target = 0 输出:4

例2: nums = [4,5,6,7,0,1,2], target = 3 输出:-1

public int search(int[] nums, int target) {
    if (nums == null || nums.length == 0) {
        return -1;
    }

    int left = 0, right = nums.length-1, mid;
    while (left <= right) {
        mid = (left + right) >> 1;
        if (nums[mid] == target) {
            return mid;
        }else if (nums[0] <= nums[mid]) {
            // mid左半边有序 & target在左半边[nums[0], nums[mid]]
            if (nums[mid] >= target && target >=nums[0]) {
                right = mid-1;
            }else {
                left = mid+1;
            }
        }else{
            // mid右半边有序 & target在右半边[nums[mid], nums[length-1]]
            if (nums[mid] <= target && target <= nums[nums.length-1]) {
                left = mid+1;
            }else {
                right = mid-1;
            }
        }
    }
    return -1;
}

关联问题_leetcode153:寻找旋转排序数组中的最小值

题目描述:给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并进行了旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

例1: nums = [3,4,5,1,2] 输出:1

例2: nums = [4,5,6,7,0,1,2] 输出:0

解法思路:二分查找法。

public int findMin(int[] nums) {
    int l = 0, r = nums.length-1;
    while (l < r) {
        // 地板除,更靠近left
        int mid = l + (r - l) / 2;
        if (nums[mid] < nums[r]) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return nums[l];
}

关联问题_leetcode154: 寻找旋转排序数组中的最小值 II

题目描述:给你一个可能存在 重复 元素值的数组 nums  ,它原来是一个升序排列的数组,并进行了旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

例1: nums = [2,2,2,0,1], 输出:0

解法思路:二分查找法。因为有重复元素,所以在判断时,如果元素相等,需要特殊处理

public int findMin(int[] nums) {
    int l = 0, r = nums.length-1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] < nums[r]) {
            r = mid;
        } else if(nums[mid] > nums[r]) {
            l = mid + 1;
        } else {
            // 无法判断哪边有序,r自减
            r = r - 1;
        }
    }
    return nums[l];
}

14、leetcode200: 岛屿数量

题目描述:

        给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。

示例 1:

grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]。 输出:1
public int numIslands(char[][] grid) {
    if(grid == null) return 0;
    
    int m = grid.length, n = grid[0].length, num = 0;
    for(int i = 0; i<m; i++) {
        for(int j = 0; j<n; j++){
            if(grid[i][j] == '1') {
                mark(grid, i, j, m, n);
                num++;  
            }
        }
    }
    return num;
}

public void mark(char[][] grid, int i, int j, int m, int n) {
    if(!inArea(i, j, m,n )) return;
    
    if(grid[i][j] != '1') {
        return;
    }

    grid[i][j] = '2';
    mark(grid, i+1, j, m, n);
    mark(grid, i-1, j, m, n);
    mark(grid, i, j+1, m, n);
    mark(grid, i, j-1, m, n);
}

public boolean inArea(int i, int j, int m, int n) {
    return i>=0 && i<m && j>= 0 && j<n;
}

关联问题_leetcode695:岛屿的最大面积

题目描述: 

        给定一个由 0 和 1 组成的非空二维数组 grid ,用来表示海洋岛屿地图。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

public int maxAreaOfIsland(int[][] grid) {
    if(grid == null) return 0;

    int m = grid.length, n = grid[0].length, max = 0;

    for(int i=0; i<m; i++) {
        for(int j=0; j<n; j++) {
            if(grid[i][j] == 1) {
                max = Math.max(max, markAndGetMaxSquare(grid, i, j, m, n));
            }
        }
    }
    return max;
}

public int markAndGetMaxSquare(int[][] grid, int i, int j, int m, int n) {
    if(!inArea(i, j, m, n)) return 0;
    
    if(grid[i][j] != 1) return 0;

    grid[i][j] = 2;
    return 1 + markAndGetMaxSquare(grid, i+1, j, m, n)
             + markAndGetMaxSquare(grid, i-1, j, m, n)
             + markAndGetMaxSquare(grid, i, j+1, m, n)
             + markAndGetMaxSquare(grid, i, j-1, m, n);
}

public boolean inArea(int i, int j, int m, int n) {
    return i>=0 && i<m && j>=0 && j<n; 
}

关联问题_leetcode463: 岛屿的周长

题目描述: 

        给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

        岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

public int islandPerimeter(int[][] grid) {
    if(grid == null) return 0;

    int m = grid.length, n = grid[0].length, max = 0;
    for(int i=0; i<m; i++){
        for(int j=0; j<n; j++) {
            if(grid[i][j] == 1) {
                max = Math.max(max, markAndGetMaxSquare(grid, i, j, m, n));
            }
        }
    }
    return max;
}

public int markAndGetMaxSquare(int[][] grid, int i, int j, int m, int n) {
    if(!inArea(i, j, m, n)) return 1;

    if(grid[i][j] == 2) return 0;

    if(grid[i][j] == 0) return 1;
    
    grid[i][j] = 2;
    
    return markAndGetMaxSquare(grid, i+1, j, m, n) 
            + markAndGetMaxSquare(grid, i-1, j, m, n)
            + markAndGetMaxSquare(grid, i, j+1, m, n)
            + markAndGetMaxSquare(grid, i, j-1, m, n);
}

public boolean inArea(int i, int j, int m, int n) {
    return i>=0 && i<m && j>=0 && j<n;
}
  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值