LeetCode-TianChi-Training

一 基础学习

1. 数组

1. 两数之和

public class TwoSum {
    /**
     * 1. 两数之和
     */
    public int[] twoSum1(int[] nums, int target) {
        /*
         * 暴力破解
         */
        int[] arr = new int[2];
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if(nums[i]+nums[j] == target){
                    arr[0] = i;
                    arr[1] = j;
                }
            }
        }
        return arr;
    }
    public int[] twoSum2(int[] nums, int target) {
        /*
         * 哈希表:一次循环,判断target-nums[i]是否在字典中,
         * 如果在,则返回对应下标,如果不在则将nums[i]作为键,下标i作为值存入字典中。
         */
        Map<Integer, Integer> hashmap = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; i++) {
            if(hashmap.containsKey(target - nums[i])){
                return new int[] {hashmap.get(target - nums[i]),i};
            }
            hashmap.put(nums[i], i);
        }
        return new int[2];
    }
}

2. 最接近的三数之和

public class ThreeSumClosest {
    /**
     * 16. 最接近的三数之和
     */
    public int threeSumClosest1(int[] nums, int target) {
        /*
         * 暴力破解
         */
        int sum = 0;
        int error = 10001;
        for (int i = 0; i < nums.length; i++) {
            for (int i1 = i + 1; i1 < nums.length; i1++) {
                for (int i2 = i1 + 1; i2 < nums.length; i2++) {
                    if (Math.abs(target - (nums[i] + nums[i1] + nums[i2])) < error) {
                        sum = nums[i] + nums[i1] + nums[i2];
                        error = Math.abs(target - sum);
                    }
                }
            }
        }
        return sum;
    }

    public int threeSumClosest2(int[] nums, int target) {
        /*
         * 排序+双指针
         * 遍历一遍数组,将nums[1]作为第一个加数,双指针p、q初始化分别指向num[i+1]与nums[nums.length-1]
         * 定义result为最终结果,当目标值与三数之和的差值 小于 目标值与result的差值时,更新result;
         * 当三数之和大于目标值时,那么将q往小的调,即将q往左移;当三数之和小于目标值时,那么将p往大的调,即将p往右移;
         * 当p == q时,表示将nums[i]作为第一个加数的所有情况遍历完了。
         */
        Arrays.sort(nums);
        int result = nums[0] + nums[1] + nums[2];
        for (int i = 0; i < nums.length; i++) {
            int p = i + 1;
            int q = nums.length - 1;
            while (p < q) {
                int sum = nums[i] + nums[p] + nums[q];
                if (Math.abs(target - sum) < Math.abs(target - result)) {
                    result = sum;
                }
                if (sum < target) {
                    p++;
                } else if (sum > target) {
                    q--;
                } else {
                    return result;
                }
            }
        }
        return result;
    }
}

3. 删除有序数组中的重复项

public class RemoveDuplicates {

    /**
     * 26. 删除有序数组中的重复项
     */
    public int removeDuplicates(int[] nums) {
        /*
         * 双指针;
         * 定义p、q指针,初始化分别指向nums[0],nums[1],前提是数组长度大于等于2
         * 若 nums[p] == nums[q] 那么 q++;
         * 若 nums[p] != nums[q] 那么 p++,nums[p] = nums[q],q++;
         * 当 p 指向最后一个值完后,终止,返回q;
         */
        // 边界判断
        if (nums.length < 2) {
            return nums.length;
        }
        int p = 0;
        int q = 1;
        for (; q < nums.length; q++) {
            if(nums[p] == nums[q]){
                continue;
            }else {
                p++;
                nums[p] = nums[q];
            }
        }
        return p+1;
    }
}

4. 移除元素

public class RemoveElement {
    /**
     * 27. 移除元素
     */
    public int removeElement1(int[] nums, int val) {
        /*
         * 双指针;只是移除元素,所以只需要遍历一次数组,当nums[i]与val相等时,那么改变nums[i]为后面与val不等的值就好了。
         * 初始化指针p、q分别指向nums[0]、nums[0];
         * 当nums[q] == val 时,q++;
         * 当nums[q] != val 时,将nums[p] = nums[q]; p++,q++;
         */
        // 边界判断
        if (nums.length == 0) {
            return 0;
        }
        int p = 0;
        for (int q = 0; q < nums.length; q++) {
            if(nums[q] != val){
                nums[p] = nums[q];
                p++;
            }
        }
        return p;
    }
}

5. 三数之和

public class ThreeSum {
    /**
     * 15. 三数之和
     */
    private List<List<Integer>> result;

    public List<List<Integer>> threeSum(int[] nums) {
        /*
         * 排序+双指针;与 "16.最接近的三数之和" 可以用同样的方法
         * 先对数组排序;然后遍历一遍数组;
         * 双指针p、q分别初始化指向 i+1 与 nums.length-1
         * 判断 nums[i] + nums[q] + nums[q]
         * 若 = 0,则添加进列表中,p++、q--
         * 若 < 0,则p++;若 > 0,则q--;当p == q时,跳出循环
         * 因为要求三个元素不能重复,所以当 nums[i] == nums[i-1]时,continue;
         * nums[p] == nums[p-1]时,continue
         * nums[q] == nums[q+1]时,continue
         */
        result = new ArrayList<List<Integer>>();
        // 边界判断
        if (nums.length < 3) {
            return result;
        }
        // 数组排序
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int p = i + 1;
            int q = nums.length - 1;
            while (p < q) {
                if (p > i + 1 && nums[p] == nums[p - 1]) {
                    p++;
                    continue;
                }
                if (q < nums.length - 1 && nums[q] == nums[q + 1]) {
                    q--;
                    continue;
                }
                if ((nums[i] + nums[p] + nums[q]) < 0) {
                    p++;
                } else if ((nums[i] + nums[p] + nums[q]) > 0) {
                    q--;
                } else {
                    // 创建一个列表,临时存储三数之和为0的值
                    List<Integer> tempList = new ArrayList<Integer>();
                    tempList.add(nums[i]);
                    tempList.add(nums[p]);
                    tempList.add(nums[q]);
                    result.add(tempList);
                    p++;
                    q--;
                }
            }
        }
        return result;
    }
}

6. 单词规律

public class WordPattern {
    /**
     * 290. 单词规律
     */
    public boolean wordPattern(String pattern, String s) {
        /*
         * 哈希表;
         * 先将s按照' '分割成单词String数组s_list,然后判断数组长度与pattern数组长度是否相同,若不同,返回false;
         * 创建一个map集合,一遍遍历,将pattern[i]作为集合的key,s_list[i]作为集合的value
         * 如果当前pattern[i]不在集合的key中,且集合的value中也没有当前的s_list[i](判断这个是为了避免出现一值对应多键的情况),
         * 那么将pattern[i]、s_list[i]作为键值对插入集合中,
         * 如果当前pattern[i]在集合的key中,那么判断当前的s_list[i]与集合中pattern[i]这个键对应的值是否相同,若不同,返回false;
         * 遍历完成后,返回true;
         */
        String[] s_list = s.split(" ");
        // 判断分割后的长度s_list与pattern长度是否相同
        if (s_list.length != pattern.length()) {
            return false;
        }
        // 创建一个map,其中建存储pattern的字符,值存储s_list的字符串
        Map<Character, String> hashmap = new HashMap<Character, String>();
        char[] patternList = pattern.toCharArray();
        for (int i = 0; i < patternList.length; i++) {
            // 如果集合中不包含当前的键和值那么进行添加
            if (!hashmap.containsKey(patternList[i])) {
                // 若出现一个值对应多个键,返回false
                if (hashmap.containsValue(s_list[i])) {
                    return false;
                }
                hashmap.put(patternList[i], s_list[i]);
            } else {
                String value = hashmap.get(patternList[i]);
                if (!value.equals(s_list[i])) {
                    return false;
                }
            }
        }
        return true;
    }
}

2. 链表

1. 链表定义

public class ListNode {
    int val;
    ListNode next;

    ListNode() {
    }

    ListNode(int val) {
        this.val = val;
    }

    ListNode(ListNode next){
        this.next = next;
    }

    ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

2. 移除链表元素

public class RemoveElements {
    /**
     * 203. 移除链表元素
     */

    /**
     * 非递归
     */
    public ListNode removeElements1(ListNode head, int val) {
        /*
         * 只需要遍历一遍链表,当遇到节点值与val相同的节点时,删除该节点
         * 注意:先要设置一个头结点(这个头结点没有值),而且不可以直接用头节点去.next,那样会导致原链表发生变化。
         */
        // 给当前链表加上一个哑节点,因为链表的头节点随时都可能被删除。
        ListNode temp = new ListNode(head);
        // 将头节点传给pre
        ListNode pre = temp;
        // 遍历链表
        while (pre.next != null) {
            if (pre.next.val == val) {
                pre.next = pre.next.next;
            } else {
                pre = pre.next;
            }
        }
        return head.next;

    }

    /**
     * 递归法
     */
    public ListNode removeElements2(ListNode head, int val) {
        /*
         * 对头结点之后的链表进行删除,取出头节点,如果头结点的值等于val,则删除头结点,即head = head.next;
         * 否则head = head.next进行上述判断之后的结果。
         * 接下来操作剩下的链表,和上述步骤一样;直到head为空时,终止;
         */
        if (head == null) {
            return head;
        }
        head.next = removeElements2(head.next, val);
        return head.val == val ? head.next : head;
    }
}

3. 合并两个有序链表

public class MergeTwoLists {
    /**
     * 21. 合并两个有序链表
     * 其中递归法有点像从尾结点开始往左合并,迭代法相当于从头结点开始往右一个一个的合并。
     */
    /**
     * 递归法
     */
    public ListNode mergeTwoLists1(ListNode list1, ListNode list2) {
        /*
         * 当list1[0]较小时,为 list1[0] + mergeTwoLists1(list1[1:], list2)
         * list2[0]较小时,同理。
         * 需要考虑的边界情况
         * 当list1或者list2为空链表时,就直接返回另一个不为空的链表
         * 当有一个链表为空时,递归结束
         */
        if (list1 == null) {
            return list2;
        } else if (list2 == null) {
            return list1;
        } else if (list1.val < list2.val) {
            list1.next = mergeTwoLists1(list1.next, list2);
            return list1;
        } else {
            list2.next = mergeTwoLists1(list1, list2.next);
            return list2;
        }
    }

    /**
     * 非递归
     */
    public ListNode mergeTwoLists2(ListNode list1, ListNode list2) {
        /*
         * 设置一个哨兵节点:哨兵,顾名思义,是用来解决边界问题的,
         * 在许多算法中,存在“邻居依赖问题”,在处理当前元素时,要涉及到它旁边那个元素。
         * 那如果当前元素是边界元素呢,它没有旁边那个元素,如果不作处理,程序就可能出错;
         * 如果对它特别对待,就会增加代码复杂性,还会降低程序效率。
         * 应用哨兵,也就是申请若干个多余的元素作为边界元素的邻居,可以完美得解决这个问题。
         *
         * 比如在链表中:单链表在插入和删除时,需要修改前驱结点的后继指针,这就形成了“邻居依赖”,
         * 链表中第一个元素没有前驱结点,如果没有特殊处理,在插入第一个结点时,就会出错。
         * 所以我们可以申请一个头结点,作为原本的第一个结点的前驱结点,问题也就解决了。
         *
         * 所以首先定义一个哨兵节点,其实也就是头节点head,然后判断 list1 与 list2 的值谁小,
         * head.next 指向小的那个链表,然后小的链表后移一位,
         * 当其中一个链表为空时,把另一个链表剩下的部分再插到后面就好了
         *
         */
        // 创建一个头结点
        ListNode head = new ListNode(0);
        // 头结点复制一份传给当前节点curNode,防止原始链表被改变
        ListNode curNode = head;

        // 循环判断插入 当有一个为空时跳出循环
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                curNode.next = list1;
                // 节点值小的链表后移一位
                list1 = list1.next;
            } else {
                curNode.next = list2;
                list2 = list2.next;
            }
            // 当前节点后移一位,指向下一个节点;当list1 或者list2其中一个为空时,curNode.next == null
            curNode = curNode.next;
        }
        // 判断哪一个链表不为空,将不为空的链表在接到curNode的后面;
        curNode.next = list1 == null ? list2 : list1;
        return head.next;
    }
}

4. 相交链表

public class GetIntersectionNode {
    /**
     * 160. 相交链表
     */
    /**
     * 暴力破解
     */
    public ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
        /*
         * 暴力破解法,遍历headA,headA的每个节点都和headB进行比对
         */
        // 边界条件
        if (headA == null || headB == null) {
            return null;
        }
        ListNode curNodeA = headA;
        ListNode curNodeB = headB;
        while (curNodeA != null) {
            while (curNodeB != null) {
                if (curNodeA == curNodeB) {
                    return curNodeA;
                }
                curNodeB = curNodeB.next;
            }
            curNodeA = curNodeA.next;
            // 将curNodeB节点又重新指向headB
            curNodeB = headB;
        }
        return new ListNode(0);
    }

    /**
     * 利用栈
     */
    public ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
        /*
         * 定义两个当前节点curNodeA与curNodeB分别指向headA与headB的头结点
         * 要判断两个链表的后面是否有共同节点,那么可以从后往前判断,
         * 两个链表都从尾结点开始如果相同那么继续往前走,直到出现不相同的节点,那最后一个相同的节点就是第一个
         * 但是链表不支持从尾向头查,所以可以利用栈
         */
        // 边界处理
        if (headA == null || headB == null) {
            return null;
        }
        // 定义两个指针分别指向两个链表的头节点
        ListNode curNodeA = headA;
        ListNode curNodeB = headB;
        // 初始化两个栈
        Stack<ListNode> stackA = new Stack<ListNode>();
        Stack<ListNode> stackB = new Stack<ListNode>();
        // 将两个链表压入栈
        while (curNodeA != null) {
            stackA.push(curNodeA);
            curNodeA = curNodeA.next;
        }
        while (curNodeB != null) {
            stackB.push(curNodeB);
            curNodeB = curNodeB.next;
        }
        // 定义一个节点表示返回值
        ListNode result = null;

        // 出栈判断,当其中一个栈为空或者两个栈的值不相等时,结束循环
        while (!stackA.empty() && !stackB.empty() && stackA.peek().equals(stackB.peek())) {

            result = stackA.pop();
            stackB.pop();

        }
        return result;
    }

    /**
     * 双指针
     */
    public ListNode getIntersectionNode3(ListNode headA, ListNode headB) {
        /*
         * 定义两个当前节点curNodeA与curNodeB分别指向headA与headB的头结点
         * 分别计算两个链表的长度,然后对长链表的开始位置与短链表的长度相同,这样一起往下判断就好了
         */
        // 边界处理
        if (headA == null || headB == null) {
            return null;
        }
        // 定义两个变量计算链表的长度
        int listALength = 0;
        int listBLength = 0;
        // 定义两个指针分别指向两个链表的头节点
        ListNode curNodeA = headA;
        ListNode curNodeB = headB;
        // 计算两个链表的长度
        while (curNodeA != null) {
            curNodeA = curNodeA.next;
            listALength++;
        }
        while (curNodeB != null) {
            curNodeB = curNodeB.next;
            listBLength++;
        }
        // 定义长链表要先往下遍历的次数
        int number = Math.abs(listALength - listBLength);
        // 定义长短链表的头指针
        ListNode shortList = listALength < listBLength ? headA : headB;
        ListNode longList = listALength < listBLength ? headB : headA;
        // 长链表先移动到后面的节点长度与短链表的长度相同的节点位置
        while (number > 0) {
            longList = longList.next;
            number--;
        }
        // 循环判断,当两个节点不同时,继续往下遍历,相同时,结束循环
        while (longList != shortList) {
            longList = longList.next;
            shortList = shortList.next;
        }
        // 若没有相同节点时,longList通过上面的循环已经变成空了,所以可以直接返回longList
        return longList;
    }

    /**
     * 利用哈希表
     */
    public ListNode getIntersectionNode4(ListNode headA, ListNode headB) {
        /*
         * 将链表A先存在哈希表中,然后遍历链表B,判断链表B的每个节点是否在链表A中,
         * 若有一个在,那后面的那些一定都在,则直接返回这个节点
         * 若没有,返回null
         */
        Set<ListNode> nodeSet = new HashSet<ListNode>();
        // 定义一个指针指向链表A的头节点
        ListNode curNode = headA;
        // 将链表A的节点存入set中
        while (curNode != null) {
            nodeSet.add(curNode);
            curNode = curNode.next;
        }
        // 当链表A存完之后 curNode = null,所以直接将 curNode 设为 headB
        curNode = headB;
        // 遍历链表B判断是否在set中
        while (curNode != null) {
            if (nodeSet.contains(curNode)) {
                return curNode;
            }
            curNode = curNode.next;
        }
        return curNode;
    }

    /**
     * 双指针进阶
     */
    public ListNode getIntersectionNode5(ListNode headA, ListNode headB) {
        /*
         * 定义两个指针curNodeA、curNodeB分别指向headA、headB,同时更新两个指针
         * 当curNodeA指针为空时,将curNodeA指向headB,不为空时,指向下一个节点;curNodeB同理
         * 当curNodeA、curNodeB两个指针指向的节点相同或都为空时,结束循环。返回它们指向的节点或null。
         *
         * 证明:
         * 假设两个链表长度分别为m、n,相交节点之前的长度分别为a、b,相交的长度为c,那么有 a+c=m,b+c=n
         * 两个链表相交:
         * 若 a==b,两个链表相交节点之前的长度一样,则遍历到相交节点那将终止;
         * 若 a!=b,指针curNodeA遍历节点长度 a+c+b,指针curNodeB遍历节点长度 b+c+a,会遇到相同节点,返回该节点。
         * 两个链表不相交:
         * 若 m==n,两个指针遍历到最后都指向null,返回null;
         * 若 m!=n,指针curNodeA遍历的节点长度为 m+n,指针curNodeB遍历的节点长度为 a+c+b,最终都指向null,返回null
         */
        // 边界处理
        if (headA == null || headB == null) {
            return null;
        }
        // 定义两个指针分别指向两个链表
        ListNode curNodeA = headA;
        ListNode curNodeB = headB;
        while (curNodeA != curNodeB) {
            curNodeA = curNodeA == null ? headB : curNodeA.next;
            curNodeB = curNodeB == null ? headA : curNodeB.next;
        }
        return curNodeA;
    }

    /**
     * 利用环,找环的入口节点
     */
    public ListNode getIntersectionNode6(ListNode headA, ListNode headB) {
        return null;
    }
}

5. 删除排序链表中的重复元素 II

public class DeleteDuplicates {
    /**
     * 82. 删除排序链表中的重复元素 II
     */
    public ListNode deleteDuplicates1(ListNode head) {
        /*
         * 哨兵节点
         * 定义两个指针dummyCopy、curNode分别指向哨兵节点与头结点,
         * 如果curNode.val == curNode.next.val(此处要注意curNode与curNode.next不能为空)
         * 那么记录下这个值为num,curNode继续往下移动,直到curNode的值不等于num了,(向下移动判断的时候,curNode也不能为空)
         * 将这一段重复的节点在dummyCopy中删除,怎样删除?dummyCopy的下一个节点直接指向值不等于num的节点
         * 若不等于,则将dummyCopy节点就等于curNode节点,然后curNode节点向下一个节点移动。
         */
        // 边界处理
        if (head == null) {
            return null;
        }
        // 定义一个哨兵节点,值为-101是因为链表值得范围为[-100,100]
        ListNode dummy = new ListNode(-101, head);
        // 将头节点复制一份传给curNode
        ListNode dummyCopy = dummy;
        // 定义当前节点指向head
        ListNode curNode = head;
        while (curNode != null && curNode.next != null) {
            if (curNode.val == curNode.next.val) {
                int num = curNode.val;
                while (curNode != null && curNode.val == num) {
                    curNode = curNode.next;
                }
                // 此处很关键,是相当于把dummyCopy断开了,将它里面值等于num的节点删除了,它的下一个节点直接指向了值不等于num的节点
                dummyCopy.next = curNode;
            } else {
                /*
                 * 此处写dumpyCopy = curNode,而非dummyCopy.next = curNode,
                 * 是因为下面curNode指向了下一个节点,dummyCopy同样也就指向了下一个节点
                 */
                dummyCopy = curNode;
                curNode = curNode.next;
            }
        }
        return dummy.next;
    }
}

6. 旋转链表

public class RotateRight {
    /**
     * 61. 旋转链表
     */
    public ListNode rotateRight1(ListNode head, int k) {
        /*
         * 链表右移k个长度;
         * 首先算出链表的长度length,然后将 k % length 得到一个向右移动的次数;
         * 然后再将链表的首尾相连,再在从尾往右数 k % length 个节点的地方断开就好了
         * 也就是说从头往尾数的第 (length - 1) - k % length 处的节点为尾结点
         */
        //边界判断
        if (head == null) {
            return head;
        }
        // 将头节点复制一份传给tempHead,放置在操作头结点的过程中使原链表发生变化
        ListNode tempHead = head;
        // 计算链表长度
        int linkedListLength = 0;
        /*
         * 当while循环结束时,tempHead 又变为了null,
         * 因为要首尾相连,即tempHead应该取到最后一个节点,然后让最后一个节点的next=head
         * 所以不可以用tempHead != null进行判断,这样会使tempHead最终变为null
         * 应该使用tempHead.next != null进行判断;
         * 当tempHead.next == null时,tempHead正好为尾节点,这样就可以直接在后面加head了
         * 这样会使计算的链表长度少1个节点,长度最后再加1
         */
        while (tempHead.next != null) {
            linkedListLength++;
            tempHead = tempHead.next;
        }
        /*
         * 因为链表不是双向的,所以不好从尾部往上数k % linkedListLength个节点,
         * 所以从头往尾数(linkedListLength - 1) - k % linkedListLength个节点找到尾节点
         */
        linkedListLength++;
        k = (linkedListLength - 1) - k % linkedListLength;
        // 使链表变成循环链表:尾节点的下一个节点为头结点
        tempHead.next = head;
        // 从左往右数(linkedListLength - 1) - k % linkedListLength个节点,找到要断开的地方,但是是从尾节点开始数的,所以长度不需要减一
        k++;
        while (k > 0) {
            k--;
            tempHead = tempHead.next;
        }
        head = tempHead.next;
        tempHead.next = null;
        return head;
    }

    public ListNode rotateRight2(ListNode head, int k) {
        /*
         * 优化后的代码
         */
        // 边界判断,当k==0或链表长度为1时,都不用移动
        if (head == null || k == 0 || head.next == null) {
            return head;
        }
        // 初始化链表长度,初始化为1,这样后面遍历到尾节点之后就不用再加一了
        int linkedListLength = 1;
        // 将头节点复制一份传给tempHead
        ListNode tempHead = head;
        while (tempHead.next != null){
            linkedListLength++;
            tempHead = tempHead.next;
        }
        // 因为是从尾节点开始数的,所以需要加一,而要找到尾节点又需要减一,所以不加不减
        k = linkedListLength - k % linkedListLength;
        // 当 k % linkedListLength == 0 时,说明移动后与移动前一样
        if (k % linkedListLength == 0){
            return head;
        }
        // 首尾相连
        tempHead.next = head;
        // 找尾节点
        while (k>0){
            k--;
            tempHead = tempHead.next;
        }
        // 头结点为的尾节点的下一个节点
        head = tempHead.next;
        // 尾节点之后为空
        tempHead.next = null;
        return head;
    }
}

3. 栈

1. 有效的括号

public class IsValid {
    /**
     * 20. 有效的括号
     */
    public boolean isValid(String s) {
        /*
         * 用栈判断,一遍遍历
         * 如果当前的字符为括号的左半边 "({[" ,那么就入栈,
         * 如果是括号的右半部分,那么就弹出栈顶元素,如果配对,那么继续,如果不配对,那么直接返回false
         * 当遍历完成后,栈应该为空,不为空则证明也有不配对的。
         * 当字符串的长度为奇数时,直接返回false。
         */
        // 字符串转字符数组
        char[] charArray = s.toCharArray();
        if (charArray.length % 2 != 0) {
            return false;
        }
        // 创建一个栈,用来存括号的左半部分
        Stack<Character> stack = new Stack<Character>();
        for (int i = 0; i < charArray.length; i++) {
            if (charArray[i] == '(' || charArray[i] == '{' || charArray[i] == '[') {
                stack.push(charArray[i]);
            } else {
                if (charArray[i] == ')' && !stack.empty()) {
                    if (!stack.pop().equals('(')) {
                        return false;
                    }
                } else if (charArray[i] == '}' && !stack.empty()) {
                    if (!stack.pop().equals('{')) {
                        return false;
                    }
                } else if (charArray[i] == ']' && !stack.empty()) {
                    if (!stack.pop().equals('[')) {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }
        return stack.empty();
    }
}

2. 逆波兰表达式求值

public class EvalRPN {
    /**
     * 150. 逆波兰表达式求值
     */
    public int evalRPN(String[] tokens) {
        /*
         * 利用栈。遍历一遍数组,如果是数字那么就入栈,如果是符号就弹出栈中的两个元素进行运算,运算后的值在入栈,
         * 最后返回栈顶元素,栈内也就剩下一个值了。
         * 注意:再进行计算的时候,应为 后出栈的数(+-*除)先出栈的数
         * 我不理解,在本地写tokens[i] == "*" 这种就可以运行,在力扣上就会抛java.lang.NumberFormatException
         */
        // 创建一个栈
        Stack<Integer> stack = new Stack();
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
            if (isNumber(token)) {
                stack.push(Integer.parseInt(tokens[i]));
            } else {
                int num2 = stack.pop();
                int num1 = stack.pop();
                if ("*".equals(token)) {
                    stack.push(num1 * num2);
                } else if ("/".equals(token)) {
                    stack.push(num1 / num2);
                } else if ("+".equals(token)) {
                    stack.push(num1 + num2);
                } else if ("-".equals(token)) {
                    stack.push(num1 - num2);
                }
            }
        }
        return stack.pop();
    }

    public boolean isNumber(String token) {
        return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
    }
}

3. 最小栈

/**
 * 155. 最小栈
 */
public class MinStack {
    /*
     * 用一个辅助栈来存储最小值,当每次插入一个值时,辅助栈也插入一个当前最小值与该值比较之后的较小值
     * 弹出时,辅助栈也弹出元素,最终可以直接返回辅助栈的最小值。
     */
    // 创建成员属性
    private Deque<Integer> stack;
    private Deque<Integer> minStack;

    public MinStack() {
        // 创建一个链表用来表示栈
        stack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        // 最小栈中添加一个最大值,方便后面比较
        minStack.add(Integer.MAX_VALUE);
    }

    public void push(int val) {
        stack.push(val);
        minStack.push(Math.min(val, minStack.peek()));
    }

    public void pop() {
        /*
         * 此处删除元素时应确保栈不为空。
         */
        stack.pop();
        minStack.pop();
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return minStack.peek();
    }
}

4. 比较含退格的字符串

public class BackspaceCompare {
    /**
     * 844. 比较含退格的字符串
     */
    public boolean backspaceCompare1(String s, String t) {
        /*
         * 将两个字符串分别压入两个栈,再压入的时候进行判断,如果当前的字符时#,那么不进栈,且当栈不为空时再往外弹一个,进行退格。
         * 入栈完成后将两个栈的元素出栈比对,一样就继续,不一样立马返回false,有一个栈为空则跳出循环。
         * 最后两个栈都为空则,返回true,否则false
         */
        // 创建两个栈
        Stack sStack = new Stack();
        Stack tStack = new Stack();
        // 将两个字符串压入栈
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (ch == '#') {
                if (!sStack.empty()) {
                    sStack.pop();
                }
            } else {
                sStack.push(ch);
            }
        }
        for (int i = 0; i < t.length(); i++) {
            char ch = t.charAt(i);
            if (ch == '#') {
                if (!tStack.empty()) {
                    tStack.pop();
                }
            } else {
                tStack.push(ch);
            }
        }
        while (!sStack.empty() && !tStack.empty()) {

            if (sStack.peek().equals(tStack.peek())) {
                sStack.pop();
                tStack.pop();
            } else {
                return false;
            }
        }
        return sStack.empty() && tStack.empty() ? true : false;
    }

    /**
     * 双指针
     */
    public boolean backspaceCompare2(String s, String t) {
        /*
         * 定义两个指针p、q分别指向s、t,指针用来记录当前待删除字符的数量,初始化为0;
         * 从后面往前面遍历,如果当前元素是'#'号时,指针++,
         * 如果当前值不是'#',在判断指针为不为0,
         * 若为0,则固定当前元素
         * 若不为0,则指针继续往左移,
         * 为false的情况:两个字符不相等或者一个字符都遍历完了,另一个还没完。
         * 当两个字符都遍历完成后,跳出循环
         */
        // 获取两个字符串的长度
        int sLength = s.length() - 1;
        int tLength = t.length() - 1;
        // 定义两个指针
        int p = 0;
        int q = 0;
        // 当两个字符串长度都为0时,跳出循环
        while (sLength >= 0 || tLength >= 0) {
            // 指针指向当前
            while (sLength >= 0) {
                if (s.charAt(sLength) == '#') {
                    p++;
                    sLength--;
                } else if (p > 0) {
                    sLength--;
                    p--;
                } else {
                    break;
                }
            }
            while (tLength >= 0) {
                if (t.charAt(tLength) == '#') {
                    q++;
                    tLength--;
                } else if (q > 0) {
                    tLength--;
                    q--;
                } else {
                    break;
                }
            }
            if (sLength >= 0 && tLength >= 0) {
                // 若字符串不相等,返回false
                if (s.charAt(sLength) != t.charAt(tLength)) {
                    return false;
                }
            } else {
                // 若一个字符当前长度为正,一个字符当前长度为负,返回false
                if (sLength >= 0 || tLength >= 0) {
                    return false;
                }
            }
            sLength--;
            tLength--;
        }
        return true;
    }
}

5. 基本计算器 II

public class Calculate {
    /**
     * 227. 基本计算器 II
     */
    public int calculate(String s) {
        /*
         * 先计算乘除运算,没有括号,但是有空格;需要遍历一遍s
         * 先定义个变量preSign来表示运算符,初始化为'+',因为式子中的数为整数,所以第一个数为=0+第一个数
         * 有的整数可能是多位数,怎样获取字符串中的整数可以包含多位数?
         * 定义一个数为num,初始化为0,num = 0 * num + s.charAt(i);
         * 遇到一个运算符就进行一次判断,若为'+'num直接入栈,'-'num变成相反数直接入栈,若为'*'、'/'弹出栈顶元素与num计算后在入栈
         * 当遇到下一个运算符或者i=n-1时,说明一个整数遍历完了,则将运算符更新为当前运算符,num = 0;
         * 最后将栈里的元素进行相加在返回。
         */
        Deque<Integer> stack = new LinkedList<Integer>();
        char perSign = '+';
        int num = 0;
        int n = s.length();
        for (int i = 0; i < n; i++) {
            // 将一个多位整数提取出来
            if (Character.isDigit(s.charAt(i))) {
                num = 0 * num + s.charAt(i) - '0';
            }
            if (!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ' || i == n - 1) {
                switch (perSign) {
                    case '+':
                        stack.push(num);
                        break;
                    case '-':
                        stack.push(-num);
                        break;
                    case '*':
                        stack.push(stack.pop() * num);
                        break;
                    default:
                        stack.push(stack.pop() / num);
                        break;
                }
                perSign = s.charAt(i);
                num = 0;
            }
        }
        int result = 0;
        while (!stack.isEmpty()) {
            result = result + stack.pop();
        }
        return result;
    }
}

4. 字符串

1. 验证回文串

public class IsPalindrome {
    /**
     * 125. 验证回文串
     */
    /**
     * 创建了新变量
     */
    public boolean isPalindrome1(String s) {
        /*
         * 双指针,定义p指向开头,定义指针q指向结尾,
         * 如果当前指针指向的字符不是字母,那么就向左或向右移动,如果两个都指向的是字符,那么进行一个比对,不一样则返回false,否则则继续,最终返回true。
         * 当两个指针碰面时则跳出while循环,而且不考虑字母大小写
         */
        String str = "";
        // 先对字符串s进行清洗
        for (int i = 0; i < s.length(); i++) {
            if (Character.isLetterOrDigit(s.charAt(i))) {
                str += s.charAt(i);
            }
        }
        // 如果没有字符返回true
        if (str.length() == 0) {
            return true;
        }
        int p = 0;
        int q = str.length() - 1;
        while (p < q) {
            if (Character.toLowerCase(str.charAt(p)) != Character.toLowerCase(str.charAt(q))) {
                return false;
            } else {
                p++;
                q--;
            }
        }
        return true;
    }

    /**
     * 原地操作
     */
    public boolean isPalindrome(String s) {
        /*
         * 双指针,定义p指向开头,定义指针q指向结尾,
         * 如果当前指针指向的字符不是字母,那么就向左或向右移动,如果两个都指向的是字符,那么进行一个比对,不一样则返回false,否则则继续,最终返回true。
         * 当两个指针碰面时则跳出while循环,而且不考虑字母大小写
         */
        int p = 0;
        int q = s.length() - 1;
        while (p < q) {
            // 此处需要加一句左指针要小于右指针
            while (p < q && !Character.isLetterOrDigit(s.charAt(p))) {
                p++;
            }
            while (q > p && !Character.isLetterOrDigit(s.charAt(q))) {
                q--;
            }
            if (Character.toLowerCase(s.charAt(p)) != Character.toLowerCase(s.charAt(q))) {
                return false;
            } else {
                p++;
                q--;
            }
        }
        return true;
    }
}

2. 验证回文字符串 Ⅱ

public class ValidPalindrome {
    /**
     * 680. 验证回文字符串 Ⅱ
     */
    /**
     * 暴力破解,超出时间限制
     */
    public boolean validPalindrome1(String s) {
        /*
         * 暴力破解:先写一个验证字符串是不是回文串的函数,不删除字符进行判断,遍历一遍s删除第i个字符判断,true则返回,false继续,最终返回false
         */
        // 边界判断
        if (s.length() < 3) {
            return true;
        }
        if (isPalindrome(s)) {
            return true;
        }
        for (int i = 0; i < s.length(); i++) {
            if (isPalindrome(remove(s, i))) {
                return true;
            }
        }
        return false;
    }

    public boolean isPalindrome(String s) {
        /*
         * 判断字符串是不是回文串,双指针,相同则同时往左和右移动,不同则返回,当两个指针相遇时,结束
         */
        int p = 0;
        int q = s.length() - 1;
        while (p < q) {
            if (s.charAt(p) != s.charAt(q)) {
                return false;
            }
            p++;
            q--;
        }
        return true;
    }

    public String remove(String s, int index) {
        /*
         * 删除第index个字符
         */
        if (s.length() <= index) {
            return s;
        }
        String str = "";
        for (int i = 0; i < s.length(); i++) {
            if (i == index) {
                continue;
            } else {
                str += s.charAt(i);
            }
        }
        return str;
    }
    
    /**
     * 贪心
     */
    public boolean validPalindrome2(String s) {
        /*
         * 双指针
         * 定义两个指针left,right;分别初始化为 0 与 s.length()-1
         * 当 s[left] == s[right] 时,left++,right--
         * 当 s[left] != s[right] 时,判断 s[left+1,right]与s[left,right-1]是不是回文串,如果是,返回true,否则false;
         */
        //边界处理
        if (s.length() < 3) {
            return true;
        }
        int left = 0;
        int right = s.length() - 1;
        while (left < right) {
            if (s.charAt(left) == s.charAt(right)) {
                left++;
                right--;
            } else {
                return isPalindrome(s, left + 1, right) || isPalindrome(s, left, right - 1);
            }
        }
        return true;
    }

    private boolean isPalindrome(String s, int left, int right) {
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

3. 字符串解码

public class DecodeString {
    // 为一个栈的方法定义一个成员变量
    public int i;

    /**
     * 394. 字符串解码
     */
    public String decodeString1(String s) {
        /*
         * 创建两个栈,一个栈存放字符串,一个栈存放数字
         * 遍历字符串s,用两个变量记录字符串,和数字
         * 若当前s.charAt(i)为字符,则累加,当到最后一个且最后一个字符不为']'时,将字符串在入栈。
         * 当遇到数字,且字符串不为空时,字符串入栈
         * 当遇到'['时,数字入栈
         * 当遇到']'时,括号里的字符串入栈,然后遍历字符串栈,将弹出来的字符串按照先出来的在后面的关系拼接在一起,
         * 直到遇到'['时表示一个括号内的的字符串全拼接在一起了,然后进行累加,并入栈
         * 最后出栈。
         */
        // 建立两个栈,一个栈存放数字,一个栈存放字符串
        Deque<String> strStack = new LinkedList<String>();
        Deque<String> numStack = new LinkedList<String>();

        // 创建一个变量缓存字符串
        String tempStr = "";
        // 创建一个变量计数
        String numStr = "";
        for (int i = 0; i < s.length(); i++) {
            if (Character.isLetter(s.charAt(i))) {
                tempStr += s.charAt(i);
                if (i == s.length() - 1 && s.charAt(i) != ']') {
                    strStack.push(tempStr);
                    tempStr = "";
                }
            } else if (Character.isDigit(s.charAt(i))) {
                numStr += s.charAt(i);

                if (!tempStr.equals("")) {
                    strStack.push(tempStr);
                    tempStr = "";
                }
            } else if (s.charAt(i) == '[') {
                numStack.push(numStr);
                numStr = "";
                strStack.push("[");
            } else {
                strStack.push(tempStr);
                tempStr = "";
                String curStr = "";
                while (!strStack.peek().equals("[")) {
                    curStr = strStack.pop() + curStr;
                }
                strStack.pop();
                int num = Integer.parseInt(numStack.pop());
                for (int i1 = 0; i1 < num; i1++) {
                    tempStr = tempStr + curStr;
                }
                strStack.push(tempStr);
                tempStr = "";
            }
        }

        // 出栈
        String result = "";
        while (!strStack.isEmpty()) {
            result = strStack.pop() + result;
        }
        return result;
    }

    /**
     * 一个栈
     */
    public String decodeString2(String s) {
        /*
         * 如果是字符就拼接在一起进栈
         * 如果是数字也拼接在一起进栈
         * 如果是'[',单独进栈
         * 如果是']'那么开始出栈,将出栈元素拼接在一起,直到遇到'[',弹出'[',此时栈顶元素必为数字
         * 弹出栈顶数字,进行拼接,然后在入栈。
         * 最后出栈拼接返回
         */
        //创建一个栈
        Deque<String> stack = new LinkedList<String>();
        // 初始化变量指针i指向当前的的s[i]
        i = 0;
        while (i < s.length()) {
            // 数字拼接在一起进栈
            if (Character.isDigit(s.charAt(i))) {
                //拼接数字
                String numStr = getDigits(s);
                // 数字入栈
                stack.push(numStr);
            } else if (Character.isLetter(s.charAt(i)) || s.charAt(i) == '[') {
                // 当前字符为字符或者'['时,入栈
                // 一个字符一个字符的进栈
                stack.push(String.valueOf(s.charAt(i++)));
            }else {
                // 当前字符为']',开始出栈,拼接一个'[]'中的字符,然后按照对应数字累加
                i++;
                // 创建一个变量缓存当前字符
                String tempStr = "";
                while (!stack.peek().equals("[")){
                    tempStr = stack.pop() + tempStr;
                }
                // 左括号出栈
                stack.pop();
                // 弹出栈顶数字
                int count = Integer.parseInt(stack.pop());
                // 定义一个变量缓存累加后的结果
                String curStr = "";
                for (int i1 = 0; i1 < count; i1++) {
                    curStr += tempStr;
                }
                // 拼接后的字符串入栈
                stack.push(curStr);
            }

        }

        // 定义一个变量缓存结果
        String result = "";
        while (!stack.isEmpty()){
            result = stack.pop() + result;
        }
        return result;
    }

    public String getDigits(String str) {
        // 此处字符串缓冲区来存储数字
        StringBuffer stringBuffer = new StringBuffer();
        while (Character.isDigit(str.charAt(i))) {
            stringBuffer.append(str.charAt(i++));
        }
        return stringBuffer.toString();
    }
}

4. Excel表列名称

public class ConvertToTitle {
    /**
     * 168. Excel表列名称
     */
    public String convertToTitle(int columnNumber) {
        /*
         * 不用建立字典,直接将数字+64转成char
         * columnNumber % 26, 得到一个余数num,str = char(num+64) + str
         * columnNumber // 26,得到一个商,如果商还大于26,那再 %,//;直到商小于26
         */
        String str = "";
        if (columnNumber <= 26) {
            str = (char) (columnNumber + 64) + str;
        }
        while (columnNumber > 26) {

            int remainder = columnNumber % 26;
            columnNumber /= 26;
            if (remainder == 0) {
                str = (char) (26 + 64) + str;
            } else {
                str = (char) (remainder + 64) + str;
            }
            if (columnNumber <= 26) {
                str = (char) (columnNumber + 64) + str;
            }
        }
        return str;
    }
}

5. 颠倒字符串中的单词

public class ReverseWords {
    /**
     * 151. 颠倒字符串中的单词
     */
    /**
     * 栈
     */
    public String reverseWords1(String s) {
        /*
         * 用栈解决,遍历一遍s,将s添加到栈中
         * 入栈:当前字符是" "时,继续,当前字符不为" "时,用一个tempStr记录当前字符,将其拼成一个完整的单词;
         * 当前字符是字符且下一个字符是" "时,tempStr入栈,并置空,或者运行到s的末尾时,tempStr入栈。
         * 出栈:用一个变量记录每次出栈的单词,当栈不为空时,在字符后面加一个空格,最后一个单词出栈后,就不用再加空格了。
         */
        // 定义一个变量缓存单词
        String tempStr = "";

        Deque<String> stack = new LinkedList<String>();
        // 入栈
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == ' ') {
                continue;
            }
            // 当前字符不是数字或者字母时,
            if(s.charAt(i) != ' '){
                tempStr += s.charAt(i);
                if((i+1 < s.length() && s.charAt(i+1) == ' ') || i == s.length() - 1){
                    stack.push(tempStr);
                    tempStr = "";
                }
            }
        }
        // 出栈
        // 创建一个变量存储结果
        String result = "";
        // 出栈
        while (!stack.isEmpty()) {
            result += stack.pop();
            if(!stack.isEmpty()){
                result += " ";
            }
        }
        return result;
    }

    /**
     * 双端队列
     */
    public String reverseWords2(String s) {
        /*
         * 将字符串一个字符一个字符的入队,每次入队前判断当前的队尾是否是空格,和当前元素是否是空格;
         * 出队时去掉队头的空格和队尾的空格
         */
        Deque<Character> deque = new LinkedList<Character>();
        deque.addFirst(s.charAt(0));
        for (int i = 1; i < s.length(); i++) {
            if(deque.peekLast().equals(' ') && s.charAt(i) == ' '){
                continue;
            }else {
                deque.addLast(s.charAt(i));
            }
        }
        // 去除头部空格
        while (!deque.isEmpty()){
            if(deque.peekFirst() == ' '){
                deque.removeFirst();
            }else {
                break;
            }
        }
        // 去除尾部空格
        while (!deque.isEmpty()){
            if(deque.peekLast() == ' '){
                deque.removeLast();
            }else {
                break;
            }
        }
        String result = "";
        String tempWord = "";
        while (!deque.isEmpty()){
            if(!deque.peekLast().equals(' ')){
                tempWord = deque.removeLast() + tempWord;
            }else {
                result += tempWord;
                result += deque.removeLast();
                tempWord = "";
            }
            if(deque.isEmpty()){
                result += tempWord;
            }
        }
        return result;
    }

    /**
     * 调用API
     */
    public String reverseWords3(String s) {
        /*
         * 先将字符串分割成字符数组,按照空格分
         * 然后在将字符数组进行反转
         * 最后将反转后的字符数组拼接
         */
        // 除去开头与结尾的空格
        s = s.trim();
        // 按照长空格正则表达式将字符串切分为字符列表
        List<String> sList = Arrays.asList(s.split("\\s+"));
        // 列表反转
        Collections.reverse(sList);
        return String.join(" ",sList);
    }
}

2 初阶学习

1. 树

1. 二叉树的最大深度

public class MaxDepth {
    /**
     * 104. 二叉树的最大深度
     */
    /**
     * DFS递归
     */
    public int maxDepth1(TreeNode root) {
        /*
         * 当节点为空时,返回0
         * 不为空时返回 1 + 左右子树最大的深度
         * 左右子树最大的深度:重复上面的过程
         */
        if (root == null) {
            return 0;
        }
        return 1 + Math.max(maxDepth1(root.left), maxDepth1(root.right));
    }

    /**
     * DFS非递归
     */
    public int maxDepth2(TreeNode root) {
        /*
         * 采用栈进行遍历,然后再加一个节点的层数栈作为辅助
         * 每次出栈时,节点和节点层数一起出栈,然后 result = max(result, 节点层数)
         * 节点的左右孩子入栈时,层数+1;
         */
        int result = 0;
        if (root == null) {
            return result;
        }
        // 创建一个栈
        Deque<TreeNode> nodeStack = new LinkedList<TreeNode>();
        Deque<Integer> levelStack = new LinkedList<Integer>();
        // 根节点入栈
        nodeStack.push(root);
        levelStack.push(1);
        while (!nodeStack.isEmpty()) {
            TreeNode curNode = nodeStack.pop();
            int curLevel = levelStack.pop();
            result = Math.max(result, curLevel);
            if (curNode.right != null) {
                nodeStack.push(curNode.right);
                levelStack.push(curLevel+1);
            }
            if (curNode.left != null) {
                nodeStack.push(curNode.left);
                levelStack.push(curLevel+1);
            }
        }
        return result;
    }

    /**
     * BFS非递归
     */
    public int maxDepth3(TreeNode root) {
        /*
         * 层序遍历
         * 创建一个队列,每一层入队时,层数加一
         */
        // 定义返回值
        int result = 0;
        if (root == null) {
            return result;
        }
        // 创建一个队列
        Deque<TreeNode> deque = new LinkedList<TreeNode>();
        // 根节点入栈
        deque.offer(root);
        while (!deque.isEmpty()) {
            // 获取每一层的节点个数
            int size = deque.size();
            // 当前层的节点出队,它的左右子节点入队
            for (int i = 0; i < size; i++) {
                TreeNode curNode = deque.poll();
                if (curNode.left != null) {
                    deque.offer(curNode.left);
                }
                if (curNode.right != null) {
                    deque.offer(curNode.right);
                }
            }
            result++;
        }
        return result;
    }
}

2. 二叉树的最小深度

public class MinDepth {
    /**
     * 111. 二叉树的最小深度
     */
    /**
     * DFS递归
     */
    public int minDepth1(TreeNode root) {
        /*
         * 难点:返回条件如何确定
         */
        // 1. 根节点为空,返回0
        if(root == null){
            return 0;
        }
        // 2. 有一个子树为空,返回另一个子树的最小深度,又因为子树为空,返回 0,所以二者相加
        int deepLeft = minDepth2(root.left);
        int deepRight = minDepth2(root.right);
        if(root.left == null || root.right == null){
            return deepLeft + deepRight + 1;
        }
        // 3. 两个子树都不为空,那么就返回最小的就好了
        return 1 + Math.min(deepLeft, deepRight);
    }

    /**
     * DFS迭代: 先序遍历
     */
    public int minDepth2(TreeNode root) {
        /*
         * 用一个节点栈和一个节点层数栈来实现,和最大路径一样
         * 但是判断最短路径的的方法变了,只有当前节点是叶子节点时才进行判断。
         */
        int result = 10000000;
        if (root == null) {
            return 0;
        }
        Deque<TreeNode> nodesStack = new LinkedList<TreeNode>();
        Deque<Integer> levelStack = new LinkedList<Integer>();
        // 根节点入栈
        nodesStack.push(root);
        levelStack.push(1);
        while (!nodesStack.isEmpty()) {
            TreeNode curNode = nodesStack.pop();
            int curLevel = levelStack.pop();
            if (curNode.right != null) {
                nodesStack.push(curNode.right);
                levelStack.push(curLevel + 1);
            }
            if (curNode.left != null) {
                nodesStack.push(curNode.left);
                levelStack.push(curLevel + 1);
            }
            // 只有当前节点是叶子节点时,才要进行对比,获得最小深度
            if (curNode.left == null && curNode.right == null) {
                result = Math.min(result, curLevel);
            }
        }
        return result;
    }

    /**
     * BFS迭代
     */
    public int minDepth3(TreeNode root) {
        /*
         * BFS就简单了,不需要全部遍历完,只需要找到第一个出现的叶子节点就好了,它所在的那一层就是最小深度
         * 循环的地方加入一个标记符号,表示找没找到第一个叶子节点。
         */
        int result = 0;
        if (root == null) {
            return result;
        }
        // 创建一个队列
        Deque<TreeNode> deque = new LinkedList<TreeNode>();
        // 根节点入队
        deque.offer(root);
        // 定义返回结果
        // 定义一个布尔变量来表示有没有遇到叶子节点,没遇到,则为true
        boolean symbol = true;
        // 当队列不为空,且没有出现叶子节点时,继续循环
        while (!deque.isEmpty() && symbol) {
            result++;
            int size = deque.size();
            for (int i = 0; i < size; i++) {
                TreeNode curNode = deque.poll();
                if (curNode.right == null && curNode.left == null) {
                    symbol = false;
                    break;
                }
                if (curNode.left != null) {
                    deque.offer(curNode.left);
                }
                if (curNode.right != null) {
                    deque.offer(curNode.right);
                }

            }
        }
        return result;
    }
}

3. 路径总和

public class HasPathSum {
    /**
     * 112. 路径总和
     */
    /**
     * DFS递归:前序遍历
     */
    public boolean hasPathSum1(TreeNode root, int targetSum) {
        /*
         * 根节点为空,返回false;
         * 从左到右,遇到叶子节点判断它的值与剩余的值一样不一样。一样返回true,否则:false
         */
        if (root == null) {
            return false;
        }
        if (root.left == null && root.right == null) {
            return root.val == targetSum;
        }
        return hasPathSum1(root.left, targetSum - root.val) || hasPathSum1(root.right, targetSum - root.val);
    }

    /**
     * DFS迭代
     */
    public boolean hasPathSum2(TreeNode root, int targetSum) {
        /*
         * 先序遍历
         * 一个节点栈,一个节点的值栈,
         * 若遇到叶子节点,判断叶子节点的值与目标值是否一样,一放就返回true
         */
        if (root == null){
            return false;
        }
        // 节点栈
        Deque<TreeNode> nodesStack = new LinkedList<TreeNode>();
        // 节点的值栈
        Deque<Integer> nodesValueStack = new LinkedList<Integer>();
        // 入栈
        nodesStack.push(root);
        nodesValueStack.push(targetSum);
        while (!nodesStack.isEmpty()){
            TreeNode curNode = nodesStack.pop();
            int curNodeValue = nodesValueStack.pop();
            // 遇到叶子节点的判断
            if(curNode.left == null && curNode.right == null && curNode.val == curNodeValue){
                return true;
            }
            // 入栈
            if(curNode.right != null){
                nodesStack.push(curNode.right);
                nodesValueStack.push(curNodeValue - curNode.val);
            }
            if(curNode.left != null){
                nodesStack.push(curNode.left);
                nodesValueStack.push(curNodeValue - curNode.val);
            }
        }
        return false;
    }

    /**
     * BFS迭代
     */
    public boolean hasPathSum3(TreeNode root, int targetSum) {
        /*
         * 与DFS迭代一样,两个队列,一个存储节点,一个存储节点对应的目标值
         */
        if(root == null){
            return false;
        }
        // 节点队列
        Deque<TreeNode> nodeQueue = new LinkedList<TreeNode>();
        // 节点值队列
        Deque<Integer> nodeValueQueue = new LinkedList<Integer>();
        // 入队
        nodeQueue.offer(root);
        nodeValueQueue.offer(targetSum);
        while (!nodeQueue.isEmpty()){
            // 节点队列长度
            int size = nodeQueue.size();
            for (int i = 0; i < size; i++) {
                // 节点出队
                TreeNode curNode = nodeQueue.poll();
                int curNodeVaule = nodeValueQueue.poll();
                if(curNode.left == null && curNode.right == null && curNode.val == curNodeVaule){
                    return true;
                }
                // 入队
                if(curNode.left != null){
                    nodeQueue.offer(curNode.left);
                    nodeValueQueue.offer(curNodeVaule - curNode.val);
                }
                if(curNode.right != null){
                    nodeQueue.offer(curNode.right);
                    nodeValueQueue.offer(curNodeVaule - curNode.val);
                }
            }
        }
        return false;
    }
}

4. 将有序数组转换为二叉搜索树

public class SortedArrayToBST {
    /**
     * 108. 将有序数组转换为二叉搜索树
     */
    /*
     * 二叉搜索树的中序遍历时一个递增序列。
     * 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
     */
    public TreeNode sortedArrayToBST(int[] nums) {
        /*
         * 找到中间的那个元素作为根节点,然后这样一直递归下去,当左边的下标大于右边的下标时,返回null.
         * 不能一边往左走,一边往右走,因为那样会导致子树高度差的绝对值大于1
         */
        return helper(nums, 0, nums.length - 1);
    }

    private TreeNode helper(int[] nums, int left, int right) {
        if (left > right) {
            return null;
        }
        // 获取中间位置(偶数的话,就是中间靠左)
        int mid = (right + left) / 2;
        // 创建根节点
        TreeNode root = new TreeNode(nums[mid]);
        // 递归建立左边数组的根节点
        root.left = helper(nums, left, mid - 1);
        // 递归建立右边数组的根节点
        root.right = helper(nums, mid + 1, right);
        return root;
    }
}

5. 二叉搜索树迭代器

public class BSTIterator {

    /**
     * 剑指 Offer II 055. 二叉搜索树迭代器
     */
    private int idx = 0;
    private List<Integer> valueList = new ArrayList<Integer>();

    public BSTIterator(TreeNode root) {
        if (root == null) {
            return;
        }
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        // stack.push(root);
        TreeNode curNode = root;
        while (!stack.isEmpty() || curNode != null) {
            while (curNode != null) {
                stack.push(curNode);
                curNode = curNode.left;
            }
            curNode = stack.pop();
            valueList.add(curNode.val);
            curNode = curNode.right;
        }
    }

    public int next() {
        return valueList.get(idx++);
    }

    public boolean hasNext() {
        return idx < valueList.size();
    }
}

2. 位运算

1. 只出现一次的数字

public class SingleNumber {
    /**
     * 136. 只出现一次的数字
     */
    // 线性时间复杂度:O(n)
    /*
     * 1)按位取反(~):对于任意 a,~a = -(a+1);
     * 2)按位与(&):
     * 3)按位异或(^) :
     * 4)按位或(|)
     */
    public int singleNumber1(int[] nums) {
        /*
         * 因为:任何数和 0 做异或运算,结果仍然是原来的数;任何数和其自身做异或运算,结果是 0;异或运算满足交换律和结合律
         * 所以直接异或就好了。
         */
        if (nums.length == 1) {
            return nums[0];
        }
        int ans = nums[0];
        for (int i = 1; i < nums.length; i++) {
            ans = ans ^ nums[i];
        }
        return ans;
    }

    public int singleNumber2(int[] nums) {
        /*
         * 之前用哈希表做的。
         */
        if (nums.length == 1) {
            return nums[0];
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(nums[0], 1);
        for (int i = 1; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {
                map.remove(Integer.valueOf(nums[i]));
                map.put(nums[i], 2);
            } else {
                map.put(nums[i], 1);
            }
        }
        int one_num = 0;
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (entry.getValue() == 1) {
                one_num = entry.getKey();
            }

        }
        return one_num;
    }
}

3. 双指针

1. 反转链表

public class ReverseList {

    /**
     * 剑指 Offer 24. 反转链表
     */
    /**
     * 栈
     */
    public ListNode reverseList1(ListNode head) {
        /*
         * 用栈来实现,定义一个栈,链表从头结点开始入栈,最后出栈再重新连在一起
         */
        // 边界处理
        if (head == null) {
            return null;
        }
        Deque<Integer> stack = new LinkedList<Integer>();
        // 头节点复制一份
        ListNode curNode = head;
        while (curNode != null) {
            stack.push(curNode.val);
            curNode = curNode.next;
        }
        // 创建头节点
        ListNode dummy = new ListNode(stack.pop());
        // 头节点复制一份
        curNode = dummy;
        while (!stack.isEmpty()) {
            ListNode next = new ListNode(stack.pop());
            curNode.next = next;
            curNode = curNode.next;
        }
        return dummy;
    }

    /**
     * 一次迭代
     */
    public ListNode reverseList2(ListNode head) {
        /*
         * 遍历列表,将每次遍历到的节点指向前一个结点
         * 那么就需要首先为头节点定义一个
         * 然后当前节点指向上一个节点的话,与后面的节点就断开了,所以需要一个节点存储后面的节点。
         * 双指针:一个指针表示前一个节点,一个指针表示当前节点,当前节点的下一个节点指向前一个节点;
         * 然后前一个节点先移到当前节点的位置,当前节点再往下移,最后当前节点为空,前一个节点是原始链表最后一个节点。
         */
        // 定义两个指针
        ListNode prev = null;
        ListNode curNode = head;
        while (curNode != null) {
            // 用一个节点存储链表断开后面的节点
            ListNode next = curNode.next;
            // 当前节点的下一个节点指向前一个节点
            curNode.next = prev;
            // 更新指针
            prev = curNode;
            curNode = next;
        }
        return prev;
    }

    /**
     * 递归
     */
    public ListNode reverseList3(ListNode head) {
        /*
         * 核心是:nk.next.next = nk
         * 相当于:nk+1.next = nk
         * 反转了,头结点的下一个要指向空
         */
        if (head == null || head.next == null) {
            return head;
        }
        //
        ListNode newHead = reverseList3(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }

    /**
     * 递归
     */
    public ListNode reverseList4(ListNode head) {
        /*
         * 每次传入两个节点(head, null)--> (head.next, head) --> …… (cur, pre):(当前节点,前一个节点)
         * 当到最后是,进行回溯,将 head.next.next = head
         */
        return recur(head, null);
    }

    private ListNode recur(ListNode cur, ListNode pre) {
        if (cur == null) {
            return pre;
        }
        ListNode res = recur(cur.next, cur);
        cur.next = pre;
        return res;
    }
}

2. 删除链表的倒数第 n 个结点

public class RemoveNthFromEnd {

    /**
     * 剑指 Offer II 021. 删除链表的倒数第 n 个结点
     */
    /**
     * 双指针
     */
    public ListNode removeNthFromEnd1(ListNode head, int n) {
        /*
         * 双指针——前后双指针
         * 定义一个指针 pLeft 指向头节点,另一个指针 pRight 指向 k-1 个节点,当 pRight 为null时,pLeft正好指向待删除结点,然后删掉就好了
         * 当添加哨兵节点后,就不需要在定义节点来记录待删除结点之前的节点了,因为 pLeft.next 才是待删除结点
         */
        // 当链表长度为1时,删除节点后,链表为null,为了方便删除,给链表的头结点前加一个哨兵节点
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pLeft = dummy;
        ListNode pRight = head;

        for (int i = 0; i < n - 1; i++) {
            pRight = pRight.next;
        }
        while (pRight.next != null) {
            pLeft = pLeft.next;
            pRight = pRight.next;
        }
        pLeft.next = pLeft.next.next;
        return dummy.next;
    }

    /**
     * 栈
     */
    public ListNode removeNthFromEnd2(ListNode head, int n) {
        /*
         * 链表入栈,当出栈到第k个节点时,删除
         * 为了避免链表长度为1删除一个节点后链表为空的现象,还得加一个哨兵节点
         */
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        Deque<ListNode> stack = new LinkedList<ListNode>();
        // 头节点复制一份
        ListNode curNode = dummy;
        // 节点入栈
        while (curNode != null) {
            stack.push(curNode);
            curNode = curNode.next;
        }
        // 定义当前待删除节点
        ListNode deletedNode = stack.peek();
        while (n > 0) {
            deletedNode = stack.pop();
            n--;
        }
        stack.peek().next = deletedNode.next;
        return dummy.next;
    }

    /**
     * 用链表长度
     */
    public ListNode removeNthFromEnd3(ListNode head, int n) {
        /*
         * 先加个头结点,然后获取到链表长度n,然后删除第n-k个节点
         */
        // 加头节点
        ListNode dummy = new ListNode(0, head);
        int size = 0;
        ListNode curNode = dummy.next;
        while (curNode != null) {
            size++;
            curNode = curNode.next;
        }
        curNode = dummy;
        for (int i = 0; i < size - n; i++) {
            curNode = curNode.next;
        }
        curNode.next = curNode.next.next;
        return dummy.next;
    }
}

3. 删除排序链表中的重复元素

public class DeleteDuplicates {
    /**
     * 83. 删除排序链表中的重复元素
     */
    /**
     * 双指针
     */
    public ListNode deleteDuplicates1(ListNode head) {
        /*
         * 双指针,定义一个指针pLeft指向头节点,pRight = pLeft.next
         * 当pRight不为空时,一直循环
         * 若 pLeft.val == pRight.val;那么pLift.next = pRight.next;
         * 若 pLeft.val != pRight.val;那么pLeft、pRight都想下一个节点移动
         */
        // 边界处理
        if (head == null) {
            return null;
        }
        ListNode pLeft = head;
        ListNode pRight = pLeft.next;
        while (pRight != null) {
            if (pLeft.val != pRight.val) {
                pLeft = pLeft.next;
                pRight = pRight.next;
            } else {
                pLeft.next = pRight.next;
                pRight = pRight.next;
            }
        }
        return head;
    }

    /**
     * 单指针
     */
    public ListNode deleteDuplicates2(ListNode head) {
        /*
         * 只判断当前节点与他的下一个节点的关系;
         * 若不等,当前节点后移;
         * 若相等,curNode.next = curNode.next.next
         * 这样循环时就不能只判断 当前节点不为null了,还要判断当前节点的下一个节点不为空;
         */
        // 边界处理
        if (head == null) {
            return null;
        }
        ListNode curNode = head;
        while (curNode != null && curNode.next != null) {
            if (curNode.val != curNode.next.val) {
                curNode = curNode.next;
            } else {
                curNode.next = curNode.next.next;
            }
        }
        return head;
    }
}

4. 环形链表

public class HasCycle {
    /**
     * 141. 环形链表
     */
    /**
     * 哈希表
     */
    public boolean hasCycle1(ListNode head) {
        /*
         * 用哈希表,遍历链表,将每次遍历的节点存在哈希表中,若当前节点在哈希表中,那么返回true,否则,false
         */
        // 边界判断
        if (head == null) {
            return false;
        }
        Map<ListNode, Integer> hashmap = new HashMap<ListNode, Integer>();
        ListNode curNode = head;
        while (curNode != null) {
            if (!hashmap.containsKey(curNode)) {
                hashmap.put(curNode, curNode.val);
                curNode = curNode.next;
            } else {
                return true;
            }
        }
        return false;
    }

    /**
     * 快慢指针
     */
    public boolean hasCycle2(ListNode head) {
        /*
         * 快慢指针,假设链表有个头结点,其中快指针一次走两个节点,慢指针一次走一个节点,当两个指针相遇时,证明有环
         * 当快指针为null是,证明无环,两个指针初始化为头结点的下一个节点与第二个节点,这样就可以直接while循环,避免了初始化为同一个节点时的相遇
         */
        // 边界处理
        if (head == null || head.next == null) {
            return false;
        }
        ListNode fastPointer = head.next;   // 相当于 dummy.next.next
        ListNode slowPointer = head;    // 相当于 dummy.next
        while (fastPointer != null && fastPointer.next != null) {
            if (fastPointer == slowPointer) {
                return true;
            } else {
                slowPointer = slowPointer.next;
                fastPointer = fastPointer.next.next;
            }
        }
        return false;
    }
}

5. 排序链表

public class SortList {
    /**
     * 148. 排序链表
     */
    public ListNode sortList1(ListNode head) {
        /*
         * 冒泡排序
         * 定义两个指针
         */
        if (head == null || head.next == null) {
            return head;
        }
        ListNode curNode = head;
        while (curNode != null) {
            head = sort(head);
            curNode = curNode.next;
        }
        return head;
    }

    private ListNode sort(ListNode head) {
        ListNode curNode = head;
        while (curNode.next != null) {
            int a = curNode.val;
            int b = curNode.next.val;
            if (a > b) {
                int temp = a;
                curNode.val = b;
                curNode.next.val = temp;
            }
            curNode = curNode.next;
        }
        return head;
    }

    /**
     * 归并排序-自上往下分割合并排序
     */
    public ListNode sortList2(ListNode head) {
        /*
         * 归并排序是分治的一种,将链表从中间分为两个字链表,将两个子链表排好序在合并,就相当于排好序了,子链表递归的分为子子链表;
         * 当链表的长度小于1或等于1时,就不需要再进行拆分和排序了;
         * 通过快慢指针,找到链表的中间节点
         */
        // 边界处理
        if (head == null || head.next == null) {
            return head;
        }

        // 快慢指针找到中间节点;快指针一次走两个,满指针一次走一个,当快指针走完时,慢指针刚好走了一半。奇数个节点找到中点,偶数个节点找到中心左边的节点。
        ListNode slow = head;
        ListNode fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        // 将链表从slow处分成两个链表
        ListNode temp = slow.next;
        slow.next = null;
        // 左右两个链表进行递归排序
        ListNode left = sortList2(head);
        ListNode right = sortList2(temp);
        // 将已经分割后的链表进行合并
        ListNode dummy = new ListNode(0);   // 定义一个哨兵节点
        ListNode res = dummy;
        while (left != null && right != null) {
            if (left.val < right.val) {
                res.next = left;
                left = left.next;
            } else {
                res.next = right;
                right = right.next;
            }
            res = res.next;
        }
        res.next = left != null ? left : right;
        return dummy.next;
    }
}

4. 搜索

1. 二分查找

public class Search {
    /**
     * 704. 二分查找
     */
    public int search(int[] nums, int target) {
        /*
         * 定义开始位置 start 与结束位置 end;mid = (start + end)/2
         * 如果 nums[mid] > target ,那么目标值在左侧,end = mid-1
         * 否则在右侧,start = mid+1
         */
        int start = 0;
        int end = nums.length - 1;
        while (start < end) {
            int mid = (start + end) / 2;
            if (nums[mid] > target) {
                end = mid - 1;
            } else if (nums[mid] < target) {
                start = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
}

2. 二叉树的中序遍历

public class InorderTraversal {
    private List<Integer> list;

    /**
     * 94. 二叉树的中序遍历
     */
    /**
     * 递归
     */
    public List<Integer> inorderTraversal1(TreeNode root) {
        /*
         * 中序遍历是:左 -> 根 -> 右
         */
        list = new ArrayList<Integer>();
        dfs(root);
        return list;
    }

    private void dfs(TreeNode root) {
        /*
         * 先往左递归,在添加值,再往右递归
         */
        if (root == null) {
            return;
        }
        dfs(root.left);
        list.add(root.val);
        dfs(root.right);
    }

    /**
     * 迭代
     */
    public List<Integer> inorderTraversal2(TreeNode root) {
        /*
         * 用栈来完成
         */
        list = new ArrayList<Integer>();
        // 边界处理
        if (root == null) {
            return list;
        }
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode curNode = root;
        while (!stack.isEmpty() || curNode != null) {
            // 先把所有左子树入栈
            while (curNode!=null){
                stack.push(curNode);
                curNode = curNode.left;
            }
            curNode = stack.pop();
            list.add(curNode.val);
            curNode = curNode.right;
        }
        return list;
    }
}

3. 对称的二叉树

public class IsSymmetric {
    /**
     * 剑指 Offer 28. 对称的二叉树
     */
    public boolean isSymmetric1(TreeNode root) {
        /*
         * 对称二叉树,只要当前根节点的左子树 == 右子树就好了
         * 用一个双端队列来完成,进行层序遍历,当节点为空时,添入"#"然后每次判断队尾与队首是否相同,若相同则继续,否则返回false
         */
        if (root == null) {
            return true;
        }
        Deque<TreeNode> nodeDeque = new LinkedList<TreeNode>();
        Deque<String> valueDeque = new LinkedList<String>();
        nodeDeque.offer(root);
        // 为了方便判断,根节点的值入队两次
        valueDeque.offer(String.valueOf(root.val));
        valueDeque.offer(String.valueOf(root.val));
        while (!nodeDeque.isEmpty()) {
            int size = valueDeque.size() / 2;
            for (int i = 0; i < size; i++) {
                // 移除队尾与队首元素并判断是否相同
                if (!valueDeque.pollFirst().equals(valueDeque.pollLast())) {
                    return false;
                }
            }
            size = nodeDeque.size();
            for (int i = 0; i < size; i++) {
                TreeNode curNode = nodeDeque.poll();
                // 添加节点的同时再加入节点的值,节点为空时值用"#"代替。
                if (curNode.left != null) {
                    nodeDeque.offer(curNode.left);
                    valueDeque.offer(String.valueOf(curNode.left.val));
                } else {
                    valueDeque.offer("#");
                }
                if (curNode.right != null) {
                    nodeDeque.offer(curNode.right);
                    valueDeque.offer(String.valueOf(curNode.right.val));
                } else {
                    valueDeque.offer("#");
                }
            }
        }
        return true;
    }

    public boolean isSymmetric2(TreeNode root) {
        /*
         * 从根节点开始
         * 层序遍历,每次入队时,控制好入队顺序,就不需要再创建节点值队列了
         * 入队时先入左子树的左节点,再入右子树的右节点;然后再入左子树的右节点,最后入右子树的左节点,就可以一一对应了
         */
        if (root == null) {
            return true;
        }
        // 创建队列
        Deque<TreeNode> deque = new LinkedList<TreeNode>();
        // 根节点入栈两次
        deque.offer(root);
        deque.offer(root);
        while (!deque.isEmpty()) {
            // 获取左右子树
            // 左右子树的可能性:1.都为null,那么将继续;2.其中一个为null,返回false;3.两个都不为null,判断值是否相等,不相等返回false;
            TreeNode left = deque.poll();
            TreeNode right = deque.poll();
            // 当节点为叶子节点时,他的左右子树将为null,那么offer函数会将null添加到队列中去,所以要先判空
            if (left == null && right == null) {
                continue;
            }
            // 返回false的条件:其中一个节点为null一个不为null,或者两个就节点的值不相等
            if ((left == null || right == null) || left.val != right.val) {
                return false;
            }
            deque.offer(left.left);
            deque.offer(right.right);
            deque.offer(left.right);
            deque.offer(right.left);
        }
        return true;
    }
}

4. 二叉搜索树中第K小的元素

public class KthSmallest {
    /**
     * 230. 二叉搜索树中第K小的元素
     */
    public int kthSmallest(TreeNode root, int k) {
        /*
         * 要找到最小元素,那么就先找到最左边的元素,然后左右左右往上走,走k个节点就好了
         * 那很显然么,二叉搜索树是:左 < 根 < 右;
         * 而中序遍历恰好是:左 -> 根 -> 右
         * 所以直接中序遍历,找到第k个节点返回即可
         */
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode curNode = root;
        while (!stack.isEmpty() || curNode != null) {
            while (curNode != null) {
                stack.push(curNode);
                curNode = curNode.left;
            }
            curNode = stack.pop();
            k--;
            if (k == 0) {
                break;
            }
            curNode = curNode.right;
        }
        return curNode.val;
    }
}

5. 二叉搜索树的第k大节点

public class KthLargest {
    /**
     * 剑指 Offer 54. 二叉搜索树的第k大节点
     */
    public int kthLargest(TreeNode root, int k) {
        /*
         * 与第k小元素一样;利用后序遍历,即可找到第k大的元素
         */
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode curNode = root;
        while (!stack.isEmpty() || curNode!=null){
            while (curNode!=null){
                stack.push(curNode);
                curNode = curNode.right;
            }
            curNode = stack.pop();
            k--;
            if(k == 0){
                break;
            }
            curNode = curNode.left;
        }
        return curNode.val;
    }
}

6. 搜索旋转排序数组

public class SearchSortArray {
    /**
     * 33. 搜索旋转排序数组
     */
    public int search1(int[] nums, int target) {
        /*
         * 思路比较冗余;将 nums 划分为两个区间:大区间、小区间,用a,b表示
         * 先判断 mid 在哪个区间中,用 nums[mid] 与 num[0] 判断,大于在a中,小于在b中
         * 若在a中:当 nums[mid] < target 时,那么 target 必在 mid 的右边,所以直接 start = mid + 1;
         *        当 nums[mid] > target 时,那么 target 有可能在 mid 左边,也有可能在区间b中,
         *          所以此处要用 target 和 nums[0]进行判断,大于在 mid 左边,小于在区间b中;
         * 同理,若在b中:当 nums[mid] > target 时,那么 target 必在 mid 的左边,所以直接 end = mid - 1;
         *        当 nums[mid] < target 时,那么 target 有可能在 mid 右边,也有可能在区间中,
         *          所以此处要用 target 和 nums[0]进行判断,小于在 mid 左边,大于在区间b中;
         * 为了避免多次写nums[0] == target,在循环开始前先对 nums[0] 与 target进行判断,若相等,直接返回0;
         */
        int start = 0;
        int end = nums.length - 1;
        if (target == nums[0]) {
            return 0;
        }
        while (start <= end) {
            int mid = (start + end) / 2;

            if (nums[mid] >= nums[0]) {
                if (nums[mid] > target) {
                    if (target > nums[0]) {
                        end = mid - 1;
                    } else {
                        start = mid + 1;
                    }
                } else if (nums[mid] < target) {
                    start = mid + 1;
                } else {
                    return mid;
                }
            } else if (nums[mid] < nums[0]) {
                if (nums[mid] > target) {
                    end = mid - 1;
                } else if (nums[mid] < target) {
                    if (target > nums[0]) {
                        end = mid - 1;
                    } else {
                        start = mid + 1;
                    }
                } else {
                    return mid;
                }
            }
        }
        return -1;
    }

    public int search2(int[] nums, int target) {
        /*
         * 思路不变,对上面的代码进行优化;在a区间中,start有两种种情况,将end的那种情况写出来,不满足end的就是start了
         * 在b区间中同理;end有两种情况,将满足start改变的条件写出来,其他的就是end的条件了
         */
        int start = 0;
        int end = nums.length - 1;
        if (target == nums[0]) {
            return 0;
        }
        while (start <= end) {
            int mid = (start + end) / 2;
            // 判断在那个区间中
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] >= nums[0]) {
                if (nums[mid] > target && target > nums[0]) {
                    end = mid - 1;
                } else {
                    start = mid + 1;
                }
            } else {
                if (target < nums[0] && nums[mid] < target) {
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }
        }
        return -1;
    }
}

3 进阶学习

1. 排序

1. 合并两个有序数组

public class Merge {
    /**
     * 88. 合并两个有序数组
     */
    public void merge1(int[] nums1, int m, int[] nums2, int n) {
        /*
         * 那就从后面往前比谁大,谁的值放到新数组的后面,若一个比完了,结束,将没比玩的移过去
         * 此过程需要创建一个数组
         */
        int[] arr = new int[n + m];
        while (n != 0 && m != 0) {
            if (nums1[m - 1] > nums2[n - 1]) {
                arr[n + m - 1] = nums1[m - 1];
                m--;
            } else {
                arr[n + m - 1] = nums2[n - 1];
                n--;
            }
        }
        if (n != 0) {
            for (int i = 0; i < n; i++) {
                arr[i] = nums2[i];
            }
        } else {
            for (int i = 0; i < m; i++) {
                arr[i] = nums1[i];
            }
        }
        for (int i = 0; i < arr.length; i++) {
            nums1[i] = arr[i];
        }

    }

    public void merge2(int[] nums1, int m, int[] nums2, int n) {
        /*
         * 两个相比,大的往后面放,然后小的前移;最后再判断一下m移完,n没移完的情况。
         */
        int t = n + m - 1;
        while (n > 0 && m > 0) {
            if (nums1[m - 1] > nums2[n - 1]) {
                nums1[t] = nums1[m - 1];
                m--;
            } else {
                nums1[t] = nums2[n - 1];
                n--;
            }
            t--;
        }
        if (n != 0) {
            for (int i = 0; i < n; i++) {
                nums1[i] = nums2[i];
            }
        }
    }
}

2. 有序数组的平方

public class SortedSquares {
    /**
     * 977. 有序数组的平方
     */
    public int[] sortedSquares1(int[] nums) {
        /*
         * 暴力破解
         */
        for (int i = 0; i < nums.length; i++) {
            nums[i] = nums[i] * nums[i];
        }
        Arrays.sort(nums);
        return nums;
    }

    public int[] sortedSquares2(int[] nums) {
        /*
         * 双指针,但要创建额外数组
         */
        int i = nums.length - 1;
        int[] arr = new int[nums.length];
        int right = nums.length - 1;
        int left = 0;
        while (left < right) {
            if (nums[left] * nums[left] >= nums[right] * nums[right]) {
                arr[i] = nums[left] * nums[left];
                left++;
            } else {
                arr[i] = nums[right] * nums[right];
                right--;
            }s
            i--;
        }
        if (left == right) {
            arr[i] = nums[right] * nums[right];
        }
        return arr;
    }
}

3. 数组的相对排序

public class RelativeSortArray {
    /**
     * 1122. 数组的相对排序
     */
    public int[] relativeSortArray1(int[] arr1, int[] arr2) {
        /*
         * 利用哈希表,先将 arr1 中的元素存储到哈希表中,存储规则:<元素值,元素出现的次数>
         * 然后对 arr2 进行遍历,将与 arr2 中的元素相同的元素先进行存储,最后剩下一些没有在arr2中出现的元素
         * 将他们存放到list中,对list进行排序,然后将list中的值在按照升序接到后面。
         */
        Map<Integer, Integer> hashmap = new HashMap<Integer, Integer>();
        // arr1存入哈希表
        for (int i = 0; i < arr1.length; i++) {
            if(!hashmap.containsKey(arr1[i])){
                hashmap.put(arr1[i], 1);
            }else {
                hashmap.put(arr1[i], hashmap.get(arr1[i]) + 1);
            }
        }
        int t = 0;
        // arr2中的元素存取
        for (int i = 0; i < arr2.length; i++) {
            for (int j = 0; j < hashmap.get(arr2[i]); j++) {
                arr1[t] = arr2[i];
                t++;
            }
            hashmap.remove(arr2[i]);
        }
        // 创建一个list存放剩下的值
        List<Integer> arrayList = new ArrayList<Integer>();
        // 定义一个迭代器
        Iterator<Integer> iterator = hashmap.keySet().iterator();
        while (iterator.hasNext()){
            Integer key = iterator.next();
            for (int i = 0; i<hashmap.get(key);i++){
                arrayList.add(key);
            }
        }
        Collections.sort(arrayList);
        for (int i = 0; i < arrayList.size(); i++) {
            arr1[t] = arrayList.get(i);
            t++;
        }
        return arr1;
    }

    /**
     * 查表法
     */
    public int[] relativeSortArray2(int[] arr1, int[] arr2) {
        /*
         * 因为arr1的值域为[0,1000],所以我们用一个长度为1001的数组来存放arr1中元素出现的次数
         */
        int[] table = new int[1001];
        for (int i = 0; i < arr1.length; i++) {
            table[arr1[i]]++;
        }
        int t = 0;
        // 将arr2中对应的元素先存放到数组中
        for (int i = 0; i < arr2.length; i++) {
            for (int j = 0; j < table[arr2[i]]; j++) {
                arr1[t] = arr2[i];
                t++;
            }
            table[arr2[i]] = 0;
        }
        // 把剩下的数组元素按照升序存放
        for (int i = 0; i < table.length; i++) {
            if(table[i] == 0){
                continue;
            }else {
                for (int j = 0; j < table[i]; j++) {
                    arr1[t] = i;
                    t++;
                }
            }
        }
        return arr1;
    }
}

4. 对链表进行插入排序

public class InsertionSortList {
    /**
     * 147. 对链表进行插入排序
     */
    public ListNode insertionSortList(ListNode head) {
        /*
         * 创建一个额外链表dummy来存储结果;每次从head中取出头节点作为curNode
         * 与dummy中的节点进行遍历比较,遇到大于curNode.val的节点,就将curNode插在他的前面,否则插在dummy末尾,并跳出;
         * 直到head为空遍历结束。
         */
        // 创建一个头结点,最为返回结果的开始
        ListNode dummy = new ListNode(-5001); // 因为head中的值最小为-5000;
        while (head != null) {
            ListNode curNode = head;
            head = head.next;
            // 头节点复制一份
            ListNode dummyTemp = dummy;
            while (dummyTemp != null) {
                if (dummyTemp.next == null || dummyTemp.next.val > curNode.val) {
                    curNode.next = dummyTemp.next;
                    dummyTemp.next = curNode;
                    break;
                } else {
                    dummyTemp = dummyTemp.next;
                }
            }
        }
        return dummy.next;
    }
}

5. 排序数组

public class SortArray {
    /**
     * 912. 排序数组
     */
    /**
     * 冒泡排序,不需要创建额外空间。会超时。
     */
    public int[] bubbleSort(int[] nums) {
        /*
         * 题目要求是升序排序,所以每次将最大的放在后面;
         * 优化:加一个标志位,记录当前轮数组中元素是否有变化,若没有,则直接返回
         */
        boolean symbol = true;
        // 第一个for循环控制的是比对的轮数,第二个for控制的是比对的位置。
        for (int i = 0; i < nums.length && symbol; i++) {
            symbol = false; // 若下面的for未执行,那么symbol为false,下轮就将退出循环。
            for (int j = 0; j < nums.length - i; j++) {
                // 每次将最大的放后面
                if (j + 1 < nums.length && nums[j] > nums[j + 1]) {
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                    symbol = false;
                }
                /*
                // 每次将最小的放后面
                if (j + 1 < nums.length && nums[j] < nums[j + 1]) {
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                    symbol = false;
                }
                */
            }
        }
        return nums;
    }

    /**
     * 快速排序,不需要额外空间
     */
    public int[] quickSort(int[] nums) {
        /*
         * 快排思路:每次找一个基点,然后两个哨兵,一个从左往右走,另一个从右往左走;
         * 如果右边那个哨兵找到比基点大的数则停下来,左边那个哨兵找到比基点小的数停下来,然后交换两个哨兵找到的数;
         * 如果找不到最后两个哨兵就会碰到一起,最后交换基点和哨兵相遇的的地方的元素;
         * 然后就将数组按照基点分成了左边比基点小,右边比基点大,然后递归左边和右边,最后的结果就是有序的了。
         * 注意:此处一定要先移动右边的哨兵,不然当最后排好序时,最后的交换可能会出错
         */
        quickSort(nums, 0, nums.length - 1);
        return nums;
    }

    private void quickSort(int[] nums, int left, int right) {
        if (left > right) {
            return;
        }
        // 将基准设为中间的元素
        int base = nums[left];
        int l = left, r = right;
        // 两个哨兵(i左边,j右边)没有相遇
        while (l < r) {
            // 一个哨兵往左走,找比基点大的数
            while (nums[r] >= base && l < r) {
                r--;
            }
            // 一个哨兵往右走,找比基点小的数
            while (nums[l] <= base && l < r) {
                l++;
            }
            // 如果满足 l<r 则交换
            if (l < r) {
                int temp = nums[l];
                nums[l] = nums[r];
                nums[r] = temp;
            }
        }
        // 最后交换基点和 l、r 相遇的地方的元素
        nums[left] = nums[l];
        nums[l] = base;
        quickSort(nums, left, l - 1); // 递归调用基准的左半边数组
        quickSort(nums, l + 1, right); // 递归调用基准的右半边数组
    }

    /**
     * 选择排序——直接选择排序,不需要额外空间。会超时。
     */
    public int[] selectSort(int[] nums) {
        /*
         * 每次从数组中选出一个最大或最小的元素放在数组后面或者前面
         */
        // 此处将最小的依次放在最前面
        for (int i = 0; i < nums.length; i++) {
            // 用下标记录最小值,方便后面的交换
            int min = i;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[min]) {
                    min = j;
                }
            }
            // 交换
            if (min != i) {
                int temp = nums[i];
                nums[i] = nums[min];
                nums[min] = temp;
            }
        }
        return nums;

    }

    /**
     * 堆排序
     * 大顶堆:双亲结点的值比每一个孩子结点的值都要大。根结点值最大
     * 小顶堆:双亲结点的值比每一个孩子结点的值都要小。根结点值最小
     */
    public int[] heapSort(int[] nums) {
        /*
         * 大顶堆:nums[i] >= nums[2i+1] && nums[i] >= nums[2i+2]
         * 小顶堆:nums[i] <= nums[2i+1] && nums[i] <= nums[2i+2]
         */
        // 构建一个大堆顶,此处 i 从 nums.length / 2 - 1 开始,当数组长度大于2时,for才会执行,才需要构建堆
        for (int i = nums.length / 2 - 1; i >= 0; i--) {
            sift(nums, i, nums.length);
        }
        // 将大顶堆的堆顶值放到数组后面,然后再继续构建大顶堆
        for (int i = nums.length - 1; i > 0; i--) {
            int temp = nums[i];
            nums[i] = nums[0];
            nums[0] = temp;
            // 将最大值放到最后面之后,剩下的元素,再重新构建大顶堆
            sift(nums, 0, i);
        }
        return nums;
    }

    private void sift(int[] nums, int parent, int len) {
        // 取出当前元素,即根节点
        int value = nums[parent];
        // 从根节点的左子树开始,也就是 parent * 2+1 处开始,child 的 child 从 child*2+1 开始
        for (int child = 2 * parent + 1; child < len; child = child * 2 + 1) {
            // 左节点的值小于右节点的值时,将child指向右节点
            if (child + 1 < len && nums[child] < nums[child + 1]) {
                child++;
            }
            // 当子节点的值大于父节点的值,将子节点值赋给父节点(不用进行交换);
            if (nums[child] > value) {
                nums[parent] = nums[child]; // 父节点的值==子节点的值
                parent = child; // 父节点在指向子节点
            } else {
                break;
            }
            nums[child] = value; // 上面没有进行交换,也就是nums[parent]的值在这块进行了交换。
        }
    }

    /**
     * 归并排序——分治法,需要额外空间
     */
    public int[] mergeSort(int[] nums) {
        /*
         * 采用分治的思想,像数组不断分割,直至到单个元素,然后在比较合并
         */
        // 创建一个 nums.length 长的数组
        int[] temp = new int[nums.length];
        mergeSort(nums, 0, nums.length - 1, temp);
        return temp;
    }

    private void mergeSort(int[] nums, int left, int right, int[] temp) {
        /*
         * 递归分割
         */
        //这里是递归结束的条件,我们是对半分,那当left==right的时候肯定大家都是只有一个元素了。
        if (left < right) {
            //对半分,比如总长度是10,left=0,right=9,mid=4确实是中间分了,0~4,5~9
            //当长度9,left=0,right=8,mid=4,0~4,5~8
            int mid = left + (right - left) / 2; // 这样写可以防止越界;
            mergeSort(nums, left, mid, temp); // 左边归并排序
            mergeSort(nums, mid + 1, right, temp); // 右边归并排序

            // 将两个有序数组合并的操作
            merge(nums, left, mid, right, temp);
        }
    }

    private void merge(int[] nums, int left, int mid, int right, int[] temp) {
        /*
         *
         */
        int i = left;
        int j = mid + 1;
        int t = 0; // 临时数组指针
        while (i <= mid && j <= right) {
            // 比较两个元素谁小,谁小谁先拷贝到temp;
            if (nums[i] < nums[j]) {
                temp[t++] = nums[i++];
            } else {
                temp[t++] = nums[j++];
            }
        }
        // 将左边未拷完的拷到数组中
        while (i <= mid) {
            temp[t++] = nums[i++];
        }
        // 将右边未拷完的拷到数组中
        while (j <= right) {
            temp[t++] = nums[j++];
        }
        t = 0;
        // 将temp中的元素拷到nums中去
        while (left <= right) {
            nums[left++] = temp[t++];
        }
    }

    /**
     * 插入排序,不需要额外空间
     */
    public int[] insertSort(int[] nums) {
        /*
         * 插入排序就相当于从第二个元素开始,从后往前比较,如果前面的大,就将前面的元素往后移,
         * 直到找到一个合适的位置,就把这个元素放到这个位置,直到数组有序。
         */
        // i 为当前需要排序的元素,从第二个开始,直到最后一个。
        for (int i = 1; i < nums.length; i++) {
            int temp = nums[i]; // temp代表当前要插入的元素。
            int j = 0; // 当下面的for循环
            // nums[j] 代表的是temp之前的元素,跟那些元素进行比较,若temp比前面的元素小,那么将前面的元素往后移。
            for (j = i - 1; j >= 0 && temp < nums[j]; j--) {
                // 第一轮,nums[j+1] = nums[i] = temp
                nums[j + 1] = nums[j]; // 将前面大的元素往后移
            }
            // 如果上面for不满足时
            nums[j + 1] = temp;
        }
        return nums;
    }

    /**
     * 计数排序,需要额外空间,相当于查表法
     */
    public int[] countSort(int[] nums) {
        /*
         * 找到数组的最大值与最小值,建立一个长为 max-min+1 的数组
         */
        int d = 0, max = nums[0], min = nums[0];
        for (int i = 0; i < nums.length; i++) {
            if (min > nums[i]) {
                min = nums[i];
            }
            if (max < nums[i]) {
                max = nums[i];
            }
        }
        // 建立一个用于计数的数组
        d = min;
        int[] count_arr = new int[max - min + 1];
        for (int i = 0; i < nums.length; i++) {
            // count_arr 中的元素代表 nums 中的元素出现的次数。
            count_arr[nums[i] - min]++;
        }
        int k = 0;
        for (int i = 0; i < nums.length; ) {
            // 在这里控制相同元素的插入,而且当count_arr中的元素值为0时,k加i不加
            if (count_arr[k] > 0) {
                nums[i] = k + min;
                i++;
                count_arr[k]--;
            } else {
                k++;
            }
        }
        return nums;
    }

    /**
     * 希尔排序,不需要额外空间
     */
    public int[] shellSort(int[] nums) {
        /*
         * 插入排序的进阶版吧,最后的行为还是插入排序,只是把每次插入排序的作用数组改变了;
         * 设原数组长度为n,每次插入排序的数组为:{n/2,n/2/2,……,1},这个也被称为希尔序列
         * 最后的1不就是对原数组进行插入排序么,但是希尔序列每次取长度的二分之一并不是最优的选择。
         */
        // 第一个循环决定比较的间隔
        for (int d = nums.length / 2; d > 0; d = d / 2) {
            // 第二个循环根据间隔将数组分为若干个序列。
            for (int i = d; i < nums.length; i++) {
                int temp = nums[i];
                int j = 0;
                // 第三个循环对若干个序列进行插入排序
                for (j = i - d; j >= 0 && temp < nums[j]; j = j - d) {
                    nums[j + d] = nums[j];
                }
                nums[j + d] = temp;
            }
        }
        return nums;
    }

    /**
     * 桶排序,需要额外空间
     */
    public int[] bucketSort(int[] nums) {
        /*
         * 桶排序:就是找到数组中的最大值与最小值,然后根据数组长度与最值创建若干个桶区间;
         * 遍历数组,将每个元素放到它所对应的那个桶中去,最后对每个桶排序,然后再将排了序的元素拿出来合并在一起。
         */
        // 边界处理
        if (nums == null || nums.length == 0) {
            return nums;
        }
        // 查找数组的最大值与最小值
        int max = nums[0];
        int min = nums[0];
        for (int i = 1; i < nums.length; i++) {
            max = Math.max(nums[i], max);
            min = Math.min(nums[i], min);
        }
        // 计算桶的数量,并创建桶
        int bucketNum = (max - min) / nums.length + 1; // 加1,是为了避免前面为0时,确保有一个桶。
        // 构建一个具有指定容器数量的列表(大小为bucketNum)
        ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<ArrayList<Integer>>(bucketNum);
        // 对每个桶初始化
        for (int i = 0; i < bucketNum; i++) {
            bucketArr.add(new ArrayList<Integer>());
        }
        // 遍历数组,将每个元素,存放到对应桶中去
        for (int i = 0; i < nums.length; i++) {
            int num = (nums[i] - min) / nums.length; // 这块不能加1,因为下标是从0开始的
            bucketArr.get(num).add(nums[i]);
        }
        // 对每个桶进行排序
        for (int i = 0; i < bucketArr.size(); i++) {
            Collections.sort(bucketArr.get(i));
        }
        // 将桶中元素又退到原数组中去
        int index = 0;
        for (int i = 0; i < bucketArr.size(); i++) {
            for (int j = 0; j < bucketArr.get(i).size(); j++) {
                nums[index++] = bucketArr.get(i).get(j);
            }
        }
        return nums;
    }
}

2. 动态规划

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值