力扣题解(持续更新)

一、双指针

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

package leetCodePractice;

/**
 * @Date 2023/9/11 17:56
 * @Author 郜宇博
 */
public class Solution27 {
    public static int removeElement(int[] nums, int val) {
        if (nums.length == 0 || nums == null) return 0;
        int firstIndex = 0;
        int secondIndex = nums.length-1;
        while (firstIndex <= secondIndex){
            if (nums[firstIndex] == val){
                swap(nums,firstIndex,secondIndex--);
            }else {
                firstIndex++;
            }
        }
        return secondIndex + 1;
    }

    private static void swap(int[] nums, int firstIndex, int secondIndex) {
        int temp;
        temp = nums[firstIndex];
        nums[firstIndex] = nums[secondIndex];
        nums[secondIndex] = temp;
    }
}

58. 最后一个单词的长度

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。

示例 1:

输入:s = "Hello World"
输出:5
解释:最后一个单词是“World”,长度为5。

示例 2:

输入:s = "   fly me   to   the moon  "
输出:4
解释:最后一个单词是“moon”,长度为4。

示例 3:

输入:s = "luffy is still joyboy"
输出:6
解释:最后一个单词是长度为6的“joyboy”。
package leetCodePractice;

/**
 * @Date 2023/9/11 19:14
 * @Author 郜宇博
 */
public class Solution58 {
    public int lengthOfLastWord(String s) {
        String[] s1 = s.split(" ");
        return s1[s1.length-1].length();
    }
    public int lengthOfLastWord2(String s) {
        //存在为空格 则去除
        char[] charArray = s.toCharArray();
        int end = charArray.length-1;
        while (end >=0 && charArray[end] == ' '){
            end--;
        }
        int start = end;
        while (start >= 0 && charArray[start] != ' '){
            start--;
        }
        return end - start;
    }
}

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

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

示例 1:

img

输入:head = [1,1,2]
输出:[1,2]

示例 2:

img

输入:head = [1,1,2,3,3]
输出:[1,2,3]

提示:

  • 链表中节点数目在范围 [0, 300]
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序 排列
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode tempNode = head.next;
        ListNode pre = head;
        while (tempNode != null){
            if (tempNode.val == pre.val){
                //重复了,需要删除
                pre.next = tempNode.next;
            }else{
                pre = tempNode;
            }
            
            tempNode = tempNode.next;
        }
        return head;
    }
}

88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

请你 合并 nums2nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。

示例 3:

输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

提示:

  • nums1.length == m + n
  • nums2.length == n
  • 0 <= m, n <= 200
  • 1 <= m + n <= 200
  • -109 <= nums1[i], nums2[j] <= 109

进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?

package leetCodePractice;

import java.util.Arrays;

/**
 * @Date 2023/9/13 11:36
 * @Author 郜宇博
 */
public class Solution88 {
    public static void main(String[] args) {
        int[] n1 = new int[]{0};
        int[] n2 = new int[]{1};
        merge2(n1,0,n2,1);
    }
    public static void merge(int[] nums1, int m, int[] nums2, int n) {
        int[] nums1Save = new int[m];
        System.arraycopy(nums1, 0, nums1Save, 0, m);
        int index1 = 0,index2 = 0;
        int sortIndex = 0;
        while (sortIndex < m + n && index1 < m && index2 < n){
            if (nums1Save[index1] < nums2[index2]){
                nums1[sortIndex++] = nums1Save[index1++];
            }else {
                nums1[sortIndex++] = nums2[index2++];
            }
        }
        //num2元素还有剩余
        if (index1 == m && index2 != n){
            for (int i = index2; i < n; i++){
                nums1[m+index2] = nums2[i];
                index2++;
            }
        }else if (index1 != m && index2 == n){
            //num1还有剩余
            for (int i = index1; i < m; i++){
                nums1[n+index1] = nums1Save[i];
                index1++;
            }
        }
    }
    public static void merge2(int[] nums1, int m, int[] nums2, int n){
        int num1Index = m-1;
        int mergeIndex = nums1.length-1;
        int num2Index = n-1;
        //遍历两个数组,比较元素,谁大谁放数组后面
        while (mergeIndex >= 0 && num2Index >= 0 && num1Index >= 0){
            if (nums2[num2Index] >= nums1[num1Index] ){
                nums1[mergeIndex] = nums2[num2Index--];
            }else if ( nums1[num1Index] > nums2[num2Index]){
                nums1[mergeIndex] = nums1[num1Index--];
            }
            mergeIndex--;
        }
        //num1还有剩余,不能使用num1Index判断,因为num1的长度有可能为0
        if (num2Index >= 0){
            System.arraycopy(nums2, 0, nums1, 0, num2Index);
        }

    }
}

125. 验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false

示例 1:

输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。

示例 2:

输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。

示例 3:

输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。

提示:

  • 1 <= s.length <= 2 * 105
  • s 仅由可打印的 ASCII 字符组成
class Solution {
    public static boolean isPalindrome(String s) {
        if (s.isEmpty() || s.length() == 1) return true;
        int left = 0;
        int right = s.length()-1;
        //
        s = s.toUpperCase();
        while (left <= right){
            while (left <= right && !testChar(s.charAt(left))){
                //不属于字母表,跳过
                left++;
            }
            while (left <= right  && !testChar(s.charAt(right))){
                //不属于字母表,跳过
                right--;
            }
            if(left > right){
                break;
            }
            //找到了字母,进行对比
            if (s.charAt(left) == s.charAt(right)) {
                left++;
                right--;
            }else{
                return false;
            }
        }
        return true;
    }
    //不属于字母
    public static boolean testChar(char c){
        return ('A'<=c && 'Z' >= c) || '0' <= c && '9'>= c;
    }
}

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false
class Solution {
    public static boolean isHappy(int n) {
        /*
         n       下个数最大值
 `       9 :     81
         99      2 * 81
         999     3 * 81
         ....    x * 81
         因此对于任何一个数,都不会无限增大下去,有上线,因此如果最后结果不为1,就是进入某个循环中
         判断是否进入循环可以采用两种方式
         1.hash存储进入过的数,每次比较
         2.快慢指针,快指针走两步,满指针走一步
         */
        int slow = n;
        int fast = getNext(n);
        while (fast != 1 && slow != fast){
            slow = getNext(slow);
            fast = getNext(getNext(fast));
        }
        return fast == 1;
    }
    public static int getNext(int number){
        int result = 0;
        while (number > 0){
            int x = number % 10;
            number /= 10;
            result += Math.pow(x,2);
        }
        return result;
    }
}

203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例 1:

image-20230914145435939

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104]
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

双指针 pre.next = cur.next;

防止对头结点额外考虑,可以加入虚拟头结点。

public static ListNode removeElements(ListNode head, int val) {
        if (head == null) return head;
        ListNode preNode = head;
        ListNode curNode = head;
        while (curNode != null){
            if (curNode.val == val){
                //如果是头结点
                if (curNode == head){
                    head = head.next;
                    preNode = head;
                }
                preNode.next = curNode.next;
            }else {
                preNode = curNode;
            }
            curNode = curNode.next;
        }
        return head;

    }
public static ListNode removeElements(ListNode head, int val) {
        if(head == null) return null;
        //添加虚拟头结点
        ListNode newHead = new ListNode(head.val);
        newHead.next = head;
        ListNode preNode = newHead;
        ListNode curNode = newHead;
        while (curNode != null){
            if (curNode.val == val){
                preNode.next = curNode.next;
            }else {
                preNode = curNode;
            }
            curNode = curNode.next;
        }
        return newHead.next;
    }

228. 汇总区间

给定一个 无重复元素有序 整数数组 nums

返回 恰好覆盖数组中所有数字最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x

列表中的每个区间范围 [a,b] 应该按如下格式输出:

  • "a->b" ,如果 a != b
  • "a" ,如果 a == b

示例 1:

输入:nums = [0,1,2,4,5,7]
输出:["0->2","4->5","7"]
解释:区间范围是:
[0,2] --> "0->2"
[4,5] --> "4->5"
[7,7] --> "7"

示例 2:

输入:nums = [0,2,3,4,6,8,9]
输出:["0","2->4","6","8->9"]
解释:区间范围是:
[0,0] --> "0"
[2,4] --> "2->4"
[6,6] --> "6"
[8,9] --> "8->9"
public List<String> summaryRanges(int[] nums) {
        int first = 0;
        int second = 0;
        List<String> list = new ArrayList<>();
        while (second <= nums.length-1){
            while (second <= nums.length-1 && nums[second] - nums[first] == second - first){
                second++;
            }
            if (nums[first] != nums[second - 1]){
                list.add(nums[first] + "->" + nums[second - 1]);
        
            }else {
                list.add(String.valueOf(nums[first]));
            }
            first = second;
        }
        return list;

    }

234. 回文链表(快慢指针+反转、赋值数组双指针)

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

示例 1:

image-20230918160548766
输入:head = [1,2,2,1]
输出:true

示例 2:

image-20230918160559415
输入:head = [1,2]
输出:false

提示:

  • 链表中节点数目在范围[1, 105]
  • 0 <= Node.val <= 9

两种方法:

public class Solution234 {
    public static boolean isPalindrome(ListNode head) {
        ListNode start = head;
        ArrayList<Integer> arrayList = new ArrayList<>();
        while (start != null){
            arrayList.add(start.val);
            start = start.next;
        }
        int front = 0;
        int back = arrayList.size()-1;
        while (front < back){
            if (! (arrayList.get(front++).equals(arrayList.get(back--)))){
                return false;
            }
        }
        return true;
    }
    public static boolean isPalindrome2(ListNode head){
        //找到中间节点的位置
        ListNode midNode =  getMidNode(head);
        //反转链表
        ListNode secondHead =  reverseList(midNode);
        //比较
        boolean flag = palindromeJudge(midNode,secondHead);
        //还原
        reverseList(midNode);
        return flag;
    }

    private static boolean palindromeJudge(ListNode midNode, ListNode secondHead) {
        ListNode node1 = midNode;
        ListNode node2 = secondHead;
        while (node2 != null){
            if (node1.val != node2.val){
                return false;
            }
            node1 = node1.next;
            node2 = node2.next;
        }
        return true;
    }

    private static ListNode reverseList(ListNode midNode) {
        ListNode preNode = null;
        ListNode cur = midNode;
        while (cur != null){
            ListNode nextNode = cur.next;
            cur.next = preNode;
            preNode = cur;
            cur = nextNode;
        }
        return preNode;
    }

    private static ListNode getMidNode(ListNode head) {
        //快慢指针
        ListNode slow = head;
        ListNode fast = head;
        while (slow.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]
public static void moveZeroes(int[] nums) {
        int numIndex = 0;

        //所有非负数都直接赋值到数组前面
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0){
                nums[numIndex++] = nums[i];
            }
        }
        for (int i = numIndex; i < nums.length; i++) {
            nums[i] = 0;
        }
}

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
public void reverseString(char[] s) {
        int left = 0;
        int right = s.length-1;
        while (left < right){
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
            left++;
            right--;
        }

    }

345. 反转字符串中的元音字母

给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。

元音字母包括 'a''e''i''o''u',且可能以大小写两种形式出现不止一次。

示例 1:

输入:s = "hello"
输出:"holle"

示例 2:

输入:s = "leetcode"
输出:"leotcede"
class Solution {
    public String reverseVowels(String s) {
        int left = 0;
        int right = s.length()-1;
        char[] charArray = s.toCharArray();
        while (left < right){
            while (left < right && noYuan(charArray[left]) ){
                left++;
            }
            while (left < right && noYuan(charArray[right]) ){
                right--;
            }
            if (left < right){
                char temp = charArray[left];
                charArray[left] = charArray[right];
                charArray[right] = temp;
            }
            left++;
            right--;
        }
        return String.valueOf(charArray);
    }
    public boolean noYuan(Character character){
        return character != 'a' &&
                character != 'e' &&
                character != 'i' &&
                character != 'o' &&
                character != 'u' &&
                character != 'A' &&
                character != 'E' &&
                character != 'I' &&
                character != 'O' &&
                character != 'U';
    }
}

392. 判断子序列

给定字符串 st ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false
public boolean isSubsequence(String s, String t) {
        if (s.length() > t.length()) return false;
        int sIndex = 0;
        int tIndex = 0;
        while (sIndex < s.length() && tIndex < t.length()){
            if (s.charAt(sIndex) == t.charAt(tIndex)){
                sIndex++;
                tIndex++;
            }else {
                tIndex++;
            }
        }
        return sIndex==s.length();
    }

541. 反转字符串 II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:

输入:s = "abcdefg", k = 2
输出:"bacdfeg"

示例 2:

输入:s = "abcd", k = 2
输出:"bacd"

提示:

  • 1 <= s.length <= 104
  • s 仅由小写英文组成
  • 1 <= k <= 104
public static String reverseStr(String s, int k) {
        //余数
        int remainder = s.length() % (k << 1);
        //商
        int quotient = s.length() / (k << 1);
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < quotient; i++) {
            char[] chars = reverseString(s.substring(i * 2 * k, i * 2 * k + k));
            stringBuilder.append(chars);
            stringBuilder.append(s, i * 2 * k + k, (i + 1) * 2 * k);
        }
        if (remainder < k) {
            char[] chars = reverseString(s.substring(s.length() - remainder));
            stringBuilder.append(chars);
        } else {
            char[] chars = reverseString(s.substring(s.length() - remainder, s.length() - remainder + k));
            stringBuilder.append(chars);
            stringBuilder.append(s, s.length() - remainder + k, s.length());
        }
        return stringBuilder.toString();
    }
/**
方法二:每次移动2k,反转前k个,最后不足k保证直接全反转
*/
    public static char[] reverseString(String str) {
        char[] array = new char[str.length()];
        for (int i = str.length() - 1; i >= 0; i--) {
            array[str.length() - 1 - i] = str.charAt(i);
        }
        return array;
    }
    public String reverseStr1(String s, int k) {
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i += 2*k){
            //Math.min(i+k, s.length()) 可以保证最后不足k时,全反转
            reverse(chars,i,Math.min(i+k, s.length()) -1);
        }
        return String.valueOf(chars);
    }
    public void reverse(char[] chars, int left ,int right){
        while (left < right){
            char temp = chars[left];
            chars[left] = chars[right];
            chars[right] = temp;
            left++;
            right--;
        }
    }

557. 反转字符串中的单词 III

给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序

示例 1:

输入:s = "Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"

示例 2:

输入: s = "God Ding"
输出:"doG gniD"
 public static String reverseWords(String s) {
        StringBuilder stringBuilder = new StringBuilder();
        String[] words = s.split(" ");
        for (String word : words) {
            stringBuilder.append(reverseWord(word)).append(" ");
        }
        stringBuilder.deleteCharAt(stringBuilder.length()-1);
        return stringBuilder.toString();
    }

    public static char[] reverseWord(String word) {
        char[] wordCharArray = word.toCharArray();
        int left = 0;
        int right = word.length() - 1;
        char temp;
        while (left < right) {
            temp = wordCharArray[left];
            wordCharArray[left++] = wordCharArray[right];
            wordCharArray[right--] = temp;
        }
        return wordCharArray;
    }

31. 下一个排列

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
  • arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]

两种方法1.

  1. 先倒序遍历数组, 找到第一个 nums[i] (前一个数比后一个数小的位置) (即nums[i] < nums[i+1]);
  2. 这个时候我们不能直接把后一个数nums[i+1] 跟前一个数nums[i]交换就完事了; 还应该从nums[i+1]-->数组末尾这一段的数据中 找出最优的那个值( 如何最优? 即比nums[i]稍微大那么一丢丢的数, 也就是 nums[i+1]-->数组末尾中, 比nums[i]大的数中最小的那个值)
  3. 找到之后, 跟num[i]交换, 这还不算是下一个排列, num[i]后面的数值还不够小, 所以还应当进升序排列
public static void nextPermutation(int[] nums) {
        for (int i = nums.length-1; i > 0; i--){
            if (nums[i-1] < nums[i]){
                //找到一个最小的与i-1交换
                int minNum = Integer.MAX_VALUE;
                int minIndex = 0;
                for (int j = i; j < nums.length;j++){
                    if (nums[j] < minNum && nums[j] > nums[i-1]){
                        minNum = nums[j];
                        minIndex = j;
                    }
                }
                int temp = nums[minIndex];
                nums[minIndex] = nums[i-1];
                nums[i-1] = temp;
                Arrays.sort(nums,i,nums.length);
                return;
            }
        }
        Arrays.sort(nums);
    }
    

方法2.

public static void nextPermutation1(int[] nums) {
        int i = nums.length-2;
        while (i >= 0){
            if (nums[i+1] > nums[i]) break;
            i--;
        }
        if (i >= 0){
            for (int j = nums.length-1;j >=0; j--){
                if (nums[j] > nums[i]) {
                    swap(nums,i,j);
                    break;
                }
            }
        }
        reverseArr(nums,i+1,nums.length-1);
    }
    public static void swap(int[] nums,int index1,int index2){
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
    public static void reverseArr(int[] nums,int start,int end){
        while (start < end){
            swap(nums,start++,end--);
        }
    }

二、二分法

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4
package leetCodePractice;

/**
 * @Date 2023/9/11 18:41
 * @Author 郜宇博
 */
public class Solution35 {
    public static int searchInsert(int[] nums, int target) {
        int firstIndex = 0;
        int secondIndex = nums.length-1;
        while (firstIndex <= secondIndex){
            if (nums[firstIndex] >= target) return firstIndex;
            if (nums[secondIndex] == target) return secondIndex;
            if (nums[secondIndex] < target) return secondIndex+1;
            firstIndex++;
            secondIndex--;
        }
        return firstIndex;

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

    }
}

69. x 的平方根

给你一个非负整数 x ,计算并返回 x算术平方根

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5

package leetCodePractice;

/**
 * @Date 2023/9/13 10:46
 * @Author 郜宇博
 */
public class Solution69 {
    public static int mySqrt(int x) {
        if ((long) x == 0) return 0;
        if ((long) x < 4) return 1;
        for (long i = 2; i <= (long) x / 2; i++){
            if (i * i == (long) x) return (int) i;
            if(i * i > (long) x) return (int) (i - 1);
        }
        return -1;
    }
    public static int mySqrt1(int x){
        if ( x == 0) return 0;
        if ((x < 4)) return 1;
        int left = 0;
        int right = x / 2;
        while (left <= right){
            int mid = (left + right) >> 1;
            if (mid == x / mid){
                return mid;
            }
            if (mid > x / mid){
                right = mid - 1;
            }else {
                left = mid + 1;
            }
        }
        return left -1;
    }
}

278. 第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例 1:

输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false 
调用 isBadVersion(5) -> true 
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。

示例 2:

输入:n = 1, bad = 1
输出:1
public int firstBadVersion(int n) {
        int left = 0;
        int right = n;
        int mid = left + ((right - left)>>1);
        while (left <= right){
            mid = left + ((right - left)>>1);
            if (isBadVersion(mid)){
                right = mid - 1;
            }else {
                if (isBadVersion(mid+1)){
                    return mid+1;
                }else {
                    left = mid + 1;
                }
            }
        }
        return mid;

    }

367. 有效的完全平方数

给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false

完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。

不能使用任何内置的库函数,如 sqrt

示例 1:

输入:num = 16
输出:true
解释:返回 true ,因为 4 * 4 = 16 且 4 是一个整数。

示例 2:

输入:num = 14
输出:false
解释:返回 false ,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。
public static boolean isPerfectSquare(int num) {
        if (num == 1 ) return true;
        //二分法
        int left = 0;
        int right = num;
        int mid = left + ((right - left) >> 1);
        while (left < right){
            mid = left + ((right - left) >> 1);
            //使用除法防止越界, <= 0判断防止除0
            if (mid <= 0 ) return false;
            if ( mid  == num / mid && num % mid == 0){
                return true;
            }else if (mid  < num / mid){
                left = mid + 1;
            }else {
                right = mid - 1;
            }
        }
        return left * left == num;
    }

374. 猜数字大小

猜数字游戏的规则如下:

  • 每轮游戏,我都会从 1n 随机选择一个数字。 请你猜选出的是哪个数字。
  • 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。

你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-110):

  • -1:我选出的数字比你猜的数字小 pick < num
  • 1:我选出的数字比你猜的数字大 pick > num
  • 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num

返回我选出的数字。

示例 1:

输入:n = 10, pick = 6
输出:6

示例 2:

输入:n = 1, pick = 1
输出:1
public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int left = 0;
        int right = n;
        int mid;
        while (left <= right){
            mid = left + (( right - left) >> 1);
            int guess = guess(mid);
            if (guess == 0){
                return mid;
            }else if (guess == -1){
                right = mid -1;
                
            }else {
                left = mid + 1;
            }
        }
        return left;

    }
}

441. 排列硬币

你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。

给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。

示例 1:

image-20230921095318537
输入:n = 5
输出:2
解释:因为第三行不完整,所以返回 2 。

示例 2:

输入:n = 8
输出:3
解释:因为第四行不完整,所以返回 3 。


方法一:枚举

方法二:二分

public int arrangeCoins(int n) {
        for (long i = 1; i <= n; i++){
            if (((1 + i) * i) >> 1 > n){
                return (int) (i -1);
            }
        }
        return n;
    }
    public int arrangeCoins1(int n) {
        long left = 1;
        long right = n;
        long mid = 0;
        while (left <= right){
            mid = left + ((right - left) >> 1);
            if ( ((1 + mid) * mid ) >> 1   == n){
                return (int) mid;
            }
            if ( ((1 + mid) * mid ) >> 1  > n  ){
                if ( ((1 + mid-1) * (mid -1) ) >> 1 <= n){
                    return (int) (mid - 1);
                }
                right = mid -1;
            }else if ( ((1 + mid) * mid ) >> 1  < n){
                left = mid + 1;
            }
        }
        return (int) left;
    }

33. 搜索旋转排序数组

整数数组 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
public int search(int[] nums, int target) {
        //二分法查找,如果查找的目标比数组最左还小,说明在右边,left = mid +1
        int left = 0, right = nums.length -1;
        while (left < right){
            int mid =  left + (( right - left) >> 1);
            if (nums[mid] == target) return mid;
            //left - mid是顺序区间
            if (nums[left ] < nums[mid]){
                //在mid左边
                if (target >= nums[left] && target < nums[mid]){
                    right = mid - 1;
                }else {
                    //在mid右边
                    left = mid + 1;
                }
            }else {//mid - right是顺序区间
                if (target <= nums[right] && target > nums[mid]){
                    left = mid + 1;
                }else {
                    right = mid -1;
                }
            }
        }
        return -1;
    }

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

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]
public int[] searchRange(int[] nums, int target) {
        if (nums.length != 0){
            int left = 0;
            int right = nums.length-1;
            while (left <= right){
                int mid = left + ((right - left) >> 1);
                if (target == nums[mid]){
                    int start = mid,end = mid;
                    while (start >= 0 && nums[start] == nums[mid]){
                        start--;
                    }
                    while ( end < nums.length && nums[end] == nums[mid]){
                        end++;
                    }
                    return new int[]{start+1,end-1};
                } else if (target < nums[mid]){
                    right = mid-1;
                }else {
                    left = mid + 1;
                }
            }
        }
        return new int[]{-1,-1};
    }

三、数学(取余,二进,异或,进制)

66. 加一

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。

示例 2:

输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。

示例 3:

输入:digits = [0]
输出:[1]
class Solution {
     public static int[] plusOne(int[] digits){
        //元素值< 9 则+1,=9则变为0
        for (int i = digits.length-1; i >=0; i--){
            digits[i] = (digits[i] +1) % 10;
            //有一位不为9,可以退出了
            if (digits[i] != 0){
                return digits;
            }
        }
        //一直进位到了d[0]位置,需要新建数组
        digits = new int[digits.length+1];
        digits[0] = 1;
        //数组创建默认元素为0,因此后面不需要赋值
        return digits;
    }
}

67. 二进制求和

给你两个二进制字符串 ab ,以二进制字符串的形式返回它们的和。

示例 1:

输入:a = "11", b = "1"
输出:"100"

示例 2:

输入:a = "1010", b = "1011"
输出:"10101"
package leetCodePractice;

/**
 * @Date 2023/9/11 20:58
 * @Author 郜宇博
 */
public class Solution67 {
    public String addBinary(String a, String b) {
        /*
        1010
        1011
        0001
        1 1
         */
        StringBuilder stringBuilder = new StringBuilder();
        //int up = 0;
        int sum = 0;
        for (int i = a.length()-1, j = b.length()-1; i>=0 || j >=0;i--,j--){
            if (i >=0){
                sum  += a.charAt(i) - '0';
            }
            if (j >= 0){
                sum += b.charAt(j) - '0';
            }
            stringBuilder.append(sum % 2);
            //需要计算进位,只有和为2需要进位
            sum /= 2;
        }
        if (sum != 0){
            stringBuilder.append("1");
        }
        return stringBuilder.reverse().toString();
    }
}

136. 只出现一次的数字

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :

输入:nums = [2,2,1]
输出:1

示例 2 :

输入:nums = [4,1,2,1,2]
输出:4

示例 3 :

输入:nums = [1]
输出:1

提示:

  • 1 <= nums.length <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104
  • 除了某个元素只出现一次以外,其余每个元素均出现两次。
public static int singleNumber(int[] nums) {
    //异或本身会等于0
        if (nums.length == 1) return nums[0];
        int ans = nums[0];
        for (int i = 1; i < nums.length; i++){
            ans ^= nums[i];
        }
        return ans;
    }

168. Excel表列名称

相关企业

给你一个整数 columnNumber ,返回它在 Excel 表中相对应的列名称。

例如:

A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28 
...

示例 1:

输入:columnNumber = 1
输出:"A"

示例 2:

输入:columnNumber = 28
输出:"AB"

示例 3:

输入:columnNumber = 701
输出:"ZY"

示例 4:

输入:columnNumber = 2147483647
输出:"FXSHRXW"

提示:

  • 1 <= columnNumber <= 231 - 1
class Solution {
    public static String convertToTitle(int columnNumber) {
        /*
        A - 1
        B- 2
        AA 26 + 1
        ZY =
         */
        if (columnNumber < 27){
             return String.valueOf((char)('A'+columnNumber-1) );
        }
        StringBuilder stringBuilder = new StringBuilder();
        while ( columnNumber > 0){
            columnNumber--;
            char addStr =  (char) ('A' + columnNumber % 26);
            stringBuilder.append(addStr);
            columnNumber /= 26;

        }
        return stringBuilder.reverse().toString();

    }
}

171. Excel 表列序号

给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回 该列名称对应的列序号

例如:

A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28 
...

示例 1:

输入: columnTitle = "A"
输出: 1

示例 2:

输入: columnTitle = "AB"
输出: 28

示例 3:

输入: columnTitle = "ZY"
输出: 701

提示:

  • 1 <= columnTitle.length <= 7
  • columnTitle 仅由大写英文组成
  • columnTitle 在范围 ["A", "FXSHRXW"]
public static int titleToNumber(String columnTitle) {
        int result = 0;
        int count = 0;
        int pow = 0;
        for (int i = columnTitle.length()-1 ; i >= 0 ; i--){
            char c = (char) (columnTitle.charAt(i) + 1);
            count = (c - 'A');
            count *= Math.pow(26,pow++);
            result  += count;
        }
        return result;
    }
//方法二:变种:因为有 26 个字母,所以相当于 26 进制,每 26 个数则向前进一位
//所以每遍历一位则ans = ans * 26 + num
    public static int titleToNumber2(String s) {
        int ans = 0;
        for(int i=0;i<s.length();i++) {
            int num = s.charAt(i) - 'A' + 1;
            ans = ans * 26 + num;
        }
        return ans;
    }

190. 颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。

提示:

  • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
  • 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825

示例 1:

输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
     因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。

示例 2:

输入:n = 11111111111111111111111111111101
输出:3221225471 (10111111111111111111111111111111)
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
     因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。

image-20230914133749696

public class Solution {
    public static int reverseBits(int n) {
        int result = 0;
        for (int i = 0; i < 32 && n != 0; i++){
            result |= (n & 1) << (31 - i);
            n >>>= 1;
        }
        return  result;11
    }
}

191. 位1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。

提示:

  • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
  • 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 3 中,输入表示有符号整数 -3

示例 1:

输入:n = 00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'
public static int hammingWeight(int n) {
        int count = 0;
        while(n != 0){
            // -1会将最后一个1后面的0 变为1, 再&自己,会将这些1消除
            n &= (n-1);
            count++;
        }
        return count;
    }

231. 2 的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

public boolean isPowerOfTwo(int n) {
        if (n > 0){
            //二进制表示是否只有一个1
            return (n & (n - 1)) == 0;
        }
        return false;

}

326. 3 的幂

给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false

整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x

class Solution {
    public boolean isPowerOfThree(int n) {
        while(n > 0 && (n % 3 == 0)){
            n /= 3;
        }
        return n == 1;
    }
}

342. 4的幂

给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false

整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4x

示例 1:

输入:n = 16
输出:true

示例 2:

输入:n = 5
输出:false

示例 3:

输入:n = 1
输出:true

两种方式:

public boolean isPowerOfFour(int n) {
        while (n > 0 && n % 4 == 0){
            n /= 4;
        }
        return n == 1;
    }
    public boolean isPowerOfFour1(int n) {
        // 0xaaaaaaaa = 1010101010101010, 0都在偶数位上,保证n的1也在偶数位上,就是4的倍数
        return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;
    }

258. 各位相加

示例 1:

输入: num = 38
输出: 2 
解释: 各位相加的过程为:
38 --> 3 + 8 --> 11
11 --> 1 + 1 --> 2
由于 2 是一位数,所以返回 2。

示例 2:

输入: num = 0
输出: 0
public static int addDigits(int num) {
        if (num < 10 ) return num;
        while (num >= 10){
            int result = 0;
            while (num > 0){
                result += num % 10;
                num /= 10;
            }
            num = result;
        }
        return num;

    }

263. 丑数

丑数 就是只包含质因数 235 的正整数。

给你一个整数 n ,请你判断 n 是否为 丑数 。如果是,返回 true ;否则,返回 false

示例 1:

输入:n = 6
输出:true
解释:6 = 2 × 3

示例 2:

输入:n = 1
输出:true
解释:1 没有质因数,因此它的全部质因数是 {2, 3, 5} 的空集。习惯上将其视作第一个丑数。

示例 3:

输入:n = 14
输出:false
解释:14 不是丑数,因为它包含了另外一个质因数 7 。
public static boolean isUgly(int n) {
        if (n == 1 || n == 2 || n == 3 || n == 5) return true;
        while (n > 0){
            if ( (n & 1) == 0){
                n /= 2;
            }else {
                if (n % 5 == 0){
                    n /= 5;
                }
                else if (n % 3 == 0){
                    n /= 3;
                }else {
                    return false;
                }
            }
            if (inUgly(n)){
                return true;
            }
        }
        return false;
    }
    public static boolean inUgly(int n){
        return n == 1 || n == 2 || n == 3 || n == 5;
    }

268. 丢失的数字

给定一个包含 [0, n]n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

示例 1:

输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:

输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 3:

输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:

输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。
public static int missingNumber(int[] nums) {
        int sum = -nums.length;
        for (int i = 0; i < nums.length; i++){
            sum += nums[i];
            sum -= i;
        }
        return  Math.abs(sum);

    }

389. 找不同

给定两个字符串 st ,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

示例 1:

输入:s = "abcd", t = "abcde"
输出:"e"
解释:'e' 是那个被添加的字母。

示例 2:

输入:s = "", t = "y"
输出:"y"

第二种方法:问题转换成求字符串中出现奇数次的字符。类似于「136. 只出现一次的数字」,使用位运算的技巧

第三种方法:求和,再相减

public char findTheDifference(String s, String t) {
        int[] letterFlag = new int[26];
        for (int i = 0; i < s.length(); i++){
            letterFlag[s.charAt(i) - 'a']++;
        }
        for (int i = 0; i < t.length(); i++){
            if (--letterFlag[t.charAt(i) - 'a'] < 0){
                return t.charAt(i);
            }
        }
        return ' ';
    }
    public char findTheDifference1(String s,String t){
        int result = 0;
        for (int i = 0; i < s.length(); i++){
            result ^= s.charAt(i);
        }
        for (int i = 0; i < t.length(); i++){
            result ^= s.charAt(i);
        }
        return (char)result;
    }
    public char findTheDifference2(String s, String t) {
        int as = 0, at = 0;
        for (int i = 0; i < s.length(); ++i) {
            as += s.charAt(i);
        }
        for (int i = 0; i < t.length(); ++i) {
            at += t.charAt(i);
        }
        return (char) (at - as);
    }

401. 二进制手表

二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。

  • 例如,下面的二进制手表读取 "4:51"
image-20230920111602911

给你一个整数 turnedOn ,表示当前亮着的 LED 的数量,返回二进制手表可以表示的所有可能时间。你可以 按任意顺序 返回答案。

小时不会以零开头:

  • 例如,"01:00" 是无效的时间,正确的写法应该是 "1:00"

分钟必须由两位数组成,可能会以零开头:

  • 例如,"10:2" 是无效的时间,正确的写法应该是 "10:02"

示例 1:

输入:turnedOn = 1
输出:["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]
public List<String> readBinaryWatch(int turnedOn) {
        //一共10个 二进制位 前4个代表hour,后6个代表minutes
        //因此将10个二进制位遍历,如果加和为turnedOnd 并且 hour<12,minutes<60 则添加一个结果
        // 10个二进制位,有pow(2,10)的可能
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 1024; i++) {
            //高四位
            int hour = i >> 6;
            //低六位
            int minute = i & 63;
            if (hour < 12 && minute < 60 && Integer.bitCount(i) == turnedOn) {
                if (minute < 10) {
                    list.add(hour + ":0" + minute);
                } else {
                    list.add(hour + ":" + minute);
                }
            }
        }
        return list;

    }

405. 数字转换为十六进制数

给定一个整数,编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用 补码运算 方法。

注意:

  1. 十六进制中所有字母(a-f)都必须是小写。
  2. 十六进制字符串中不能包含多余的前导零。如果要转化的数为0,那么以单个字符'0'来表示;对于其他情况,十六进制字符串中的第一个字符将不会是0字符。
  3. 给定的数确保在32位有符号整数范围内。
  4. 不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。

示例 1:

输入:
26

输出:
"1a"

示例 2:

输入:
-1

输出:
"ffffffff"
 public static String toHex(int num) {
        if (num == 0) return "0";
        int groupNum = 0;
        StringBuilder stringBuilder = new StringBuilder();
        //32位分为八组,每组4位,所以每组最大值为15,总共拼8次
        for (int i = 7; i >=0; i--){
            //获得每组的值
            groupNum = (num >> (i * 4)) & 0x0000000f;
            if (groupNum > 0 || stringBuilder.length() > 0){
                char digit = groupNum < 10 ? (char) ('0' + groupNum) : (char) ('a' + groupNum - 10);
                stringBuilder.append(digit);
            }
        }
        return stringBuilder.toString();
    }

461. 汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

给你两个整数 xy,计算并返回它们之间的汉明距离。

示例 1:

输入:x = 1, y = 4
输出:2
解释:
1   (0 0 0 1)
4   (0 1 0 0)
       ↑   ↑
上面的箭头指出了对应二进制位不同的位置。
public int hammingDistance(int x, int y) {
        int n = x ^ y;
        int count = 0;
        while (n > 0){
            n &= (n-1);
            count++;
        }
        return count;
    }

476. 数字的补数

对整数的二进制表示取反(0110)后,再转换为十进制表示,可以得到这个整数的补数。

  • 例如,整数 5 的二进制表示是 "101" ,取反后得到 "010" ,再转回十进制表示得到补数 2

给你一个整数 num ,输出它的补数。

示例 1:

输入:num = 5
输出:2
解释:5 的二进制表示为 101(没有前导零位),其补数为 010。所以你需要输出 2 。

示例 2:

输入:num = 1
输出:0
解释:1 的二进制表示为 1(没有前导零位),其补数为 0。所以你需要输出 0 。
public int findComplement(int num) {
        //获取最高位的1
        int highBit = 0;
        for (int i = 1; i < 31; i++){
            if (num >= 1 << i) {
                //一直赋值,防止出现Integer的最大值
                highBit = i;
            } else {
                break;
            } 
        }
        //获取mask , 用全是1的掩码 异或 num可以得到反码
        //int 最大值为:0111 1111 1111 1111 1111 1111 1111 1111,所以用该值当做最大掩码
        int mask = Integer.MAX_VALUE;
        if (highBit < 30){
            mask = (1  << (highBit + 1)) - 1;
        }
        return mask ^ num;
    }

492. 构造矩形

作为一位web开发者, 懂得怎样去规划一个页面的尺寸是很重要的。 所以,现给定一个具体的矩形页面面积,你的任务是设计一个长度为 L 和宽度为 W 且满足以下要求的矩形的页面。要求:

  1. 你设计的矩形页面必须等于给定的目标面积。
  2. 宽度 W 不应大于长度 L ,换言之,要求 L >= W
  3. 长度 L 和宽度 W 之间的差距应当尽可能小。

返回一个 数组 [L, W],其中 LW 是你按照顺序设计的网页的长度和宽度

示例1:

输入: 4
输出: [2, 2]
解释: 目标面积是 4, 所有可能的构造方案有 [1,4], [2,2], [4,1]。
但是根据要求2,[1,4] 不符合要求; 根据要求3,[2,2] 比 [4,1] 更能符合要求. 所以输出长度 L 为 2, 宽度 W 为 2。

示例 2:

输入: area = 37
输出: [37,1]
 /*
    从 sqrt(area) 往后遍历
     */
public int[] constructRectangle(int area) {
        for (int i = (int) Math.sqrt(area); i >=1; i--){
            if (area % i == 0){
                return new int[]{area/i,i};
            }
        }
        return new int[]{area,1};
    }

504. 七进制数

给定一个整数 num,将其转化为 7 进制,并以字符串形式输出。

示例 1:

输入: num = 100
输出: "202"

示例 2:

输入: num = -7
输出: "-10"
public String convertToBase7(int num) {
        boolean negative = num < 0;
        num = Math.abs(num);
        StringBuilder stringBuilder = new StringBuilder();
        while (num > 0){
            stringBuilder.append(num % 7);
            num /= 7;
        }
        if (negative){
            stringBuilder.append('-');
        }
        return stringBuilder.reverse().toString();
    }

507. 完美数

对于一个 正整数,如果它和除了它自身以外的所有 正因子 之和相等,我们称它为 「完美数」

给定一个 整数 n, 如果是完美数,返回 true;否则返回 false

示例 1:

输入:num = 28
输出:true
解释:28 = 1 + 2 + 4 + 7 + 14
1, 2, 4, 7, 和 14 是 28 的所有正因子。

示例 2:

输入:num = 7
输出:false
public boolean checkPerfectNumber(int num) {
        int sum = 0;
        for (int i = 1; i <= num >> 1; i++){
            if (num % i  == 0){
                sum += i;
            }
        }
        return sum == num;
    }

598. 范围求和 II

给你一个 m x n 的矩阵 M ,初始化时所有的 0 和一个操作数组 op ,其中 ops[i] = [ai, bi] 意味着当所有的 0 <= x < ai0 <= y < bi 时, M[x][y] 应该加 1。

执行完所有操作后 ,计算并返回 矩阵中最大整数的个数

示例 1:

img

输入: m = 3, n = 3,ops = [[2,2],[3,3]]
输出: 4
解释: M 中最大的整数是 2, 而且 M 中有4个值为2的元素。因此返回 4。

示例 2:

输入: m = 3, n = 3, ops = [[2,2],[3,3],[3,3],[3,3],[2,2],[3,3],[3,3],[3,3],[2,2],[3,3],[3,3],[3,3]]
输出: 4

示例 3:

输入: m = 3, n = 3, ops = []
输出: 9
public int maxCount(int m, int n, int[][] ops) {
        if (ops.length == 0){
            return m * n;
        }
        /*
        求最小横、纵坐标
         */
        int minX = Integer.MAX_VALUE,minY = Integer.MAX_VALUE;
        for (int[] op : ops){
            minX = Math.min(op[0],minX);
            minY = Math.min(op[1],minY);
        }
        if (minX == 0 || minY == 0){
            return minX ==0?minY:minX;
        }
        return minX * minY;
    }

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

img

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int carry = 0,sumNoCarry;
        ListNode root = new ListNode(0);
        ListNode cur = root;
        while (l1 != null || l2 != null){
            int x = l1 == null?0: l1.val;
            int y = l2 == null?0: l2.val;;
            sumNoCarry = carry;
            sumNoCarry += x + y;
            carry = sumNoCarry / 10;
            cur.next = new ListNode(sumNoCarry % 10);
            cur = cur.next;
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
        }
        if (carry != 0){
            cur.next = new ListNode(carry);
        }
        return root.next;
    }

7. 整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1:

输入:x = 123
输出:321

示例 2:

输入:x = -123
输出:-321

示例 3:

输入:x = 120
输出:21
class Solution {
 public static int reverse(int x) {
      boolean sign = x > 0;
      x = Math.abs(x);
      int result = 0,a;
      while (x > 0){
          a = x % 10;
          x /= 10;
          if ((result == Integer.MAX_VALUE/10 && a < Integer.MAX_VALUE % 10) || result < Integer.MAX_VALUE/10){
              result = result * 10 + a;
          }else {
              return 0;
          }
      }
      return sign?result:result*-1;
  }
}

四、分治法

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

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

示例 1:

img

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

示例 2:

img

输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums严格递增 顺序排列
/**
 * @Date 2023/9/13 18:31
 * @Author 郜宇博
 */
public class Solution108 {
    public TreeNode sortedArrayToBST(int[] nums) {
        return DFS(0,nums.length-1,nums);
    }
    public static TreeNode DFS(int left, int right, int[] nums){
        //出口
        if (left > right){
            return null;
        }
        int mid = left + (right - left) >> 1;
        int val = nums[mid];
        TreeNode curRoot = new TreeNode(val);
        curRoot.left = DFS(left,mid-1,nums);
        curRoot.right = DFS(mid+1,right,nums);
        return curRoot;
    }
}

五、摩尔投票法 求众数

这里我们需要理解摩尔投票中何为占多数元素,也就是元素总占比超过一个比例。\

如果是选出占比最多的一个元素,那么占比需要是大于1/2。`
`如果是选出占比最多的两个元素,那么各个元素占比均需要大于1/3。`
`如果是选出占比最多的m个元素,那么各个元素占比需要是大于1/(m + 1)。

算法思路

第一步,初始化候选人们candidates以及候选人的票数。
第二步,扫描arrays:
扫描过程中候选人的替换以及票数增减规则如下

  1. 如果与某个候选人匹配,该候选人票数加1,继续扫描arrays,重新开始匹配。
  2. 如果与所有候选人都不匹配,检查候选人票数,如果为0,替换该候选人,不再往下检查。
  3. 如果与所有候选人都不匹配,检查候选人票数,如果不为0,继续检查一个候选人。 第三步,扫描结束以后,检查所有候选人的票数是否大于1/(candidates.length + 1)加以验证。如果大于,则候选人成立,不大于则候选人剔除掉。

从算法思路上来看,其实摩尔投票算法的核心思想就是相互抵消
下面就是摩尔投票算法的典型代表题目。

169. 多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:nums = [3,2,3]
输出:3

示例 2:

输入:nums = [2,2,1,1,1,2,2]
输出:2
class Solution {
    public int majorityElement(int[] nums) {
        if (nums.length == 1) return nums[0];
        /*
               众数: 占据一半以上的元素
               因此可以维持一个变量用于统计元素出现的次数,和假设的众数
               假设每个数都有可能是众数,遇到自己就投票,变量+1,
               不是自己就变量-1
               变量减到0的时候设置众数为自己

               原理:每个元素都投自己一票,不属于自己的就-1,由于众数的数量大于一半,因此肯定是众数获胜
         */
        int major = nums[0];
        int count = 0;
        for (int i = 0; i < nums.length; i++){
            //众数是自己
            if (nums[i] == major){
                count++;
            }
            //众数不是自己
            if (nums[i] != major){
                if (count == 1){
                    major = nums[i];
                }else{
                    count--;
                }
            }
        }
        return major;
    }
}

六、Hash存储

205. 同构字符串

给定两个字符串 st ,判断它们是否是同构的。

如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。

每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。

示例 1:

输入:s = "egg", t = "add"
输出:true

示例 2:

输入:s = "foo", t = "bar"
输出:false

示例 3:

输入:s = "paper", t = "title"
输出:true
class Solution {
    public static boolean isIsomorphic(String s, String t) {
        if (s.length() == 1 ) return true;
        HashMap<Character,Character> map = new HashMap<>();
        HashMap<Character,Character> map2 = new HashMap<>();
        for (int i = 0; i < s.length();i++){
            if (map.containsKey(s.charAt(i)) && map.get(s.charAt(i)) != t.charAt(i)){
                return false;
            }else if (!map.containsKey(s.charAt(i))){
                map.put(s.charAt(i),t.charAt(i));
            }
        }
        for (int i = 0; i < s.length();i++){
            if (map2.containsKey(t.charAt(i)) && map2.get(t.charAt(i)) != s.charAt(i)){
                return false;
            }else if (!map2.containsKey(t.charAt(i))){
                map2.put(t.charAt(i),s.charAt(i));
            }
        }
        return true;
    }
}

217. 存在重复元素

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false

示例 1:

输入:nums = [1,2,3,1]
输出:true

示例 2:

输入:nums = [1,2,3,4]
输出:false

示例 3:

输入:nums = [1,1,1,3,3,4,3,2,4,2]
输出:true
class Solution {
    public static boolean containsDuplicate(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<>();
        if (nums.length == 1 ) return false;
        for (int num : nums) {
            if (!map.containsKey(num)){
                map.put(num,1);
            }else if (map.get(num) == 1){
                return true;
            }
        }
        return false;
    }
}

242. 有效的字母异位词

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

注意:*s**t* 中每个字符出现的次数都相同,则称 *s**t* 互为字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母

两种方法,一种用hash表存储,一种用26个char[]存储

public static boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) return false;
        HashMap<Character,Integer> map = new HashMap<>();
        //存储s的字母出现次数
        for (int i = 0; i < s.length(); i++){
            if (!map.containsKey(s.charAt(i))){
                map.put(s.charAt(i),1);
            }else {
                map.put(s.charAt(i),map.get(s.charAt(i))+1);
            }
        }
        for (int i = 0; i < t.length(); i++){
            if (!map.containsKey(t.charAt(i)) || map.get(t.charAt(i)) == 0){
                return false;
            }else {
                map.put(t.charAt(i),map.get(t.charAt(i))-1);
            }
        }
        return true;
    }
    public static boolean isAnagram1(String s, String t) {
        if (s.length() != t.length()) return false;
        char[] letters = new char[26];
        for (int i = 0; i < s.length(); i++){
            int letterIndex = s.charAt(i) - 'a';
            letters[letterIndex]++;
        }
        for (int i = 0; i < t.length(); i++){
            int letterIndex = t.charAt(i) - 'a';
            if (letters[letterIndex] == 0){
                return false;
            }
            letters[letterIndex]--;
        }
        return true;
    }

290. 单词规律

给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。

示例1:

输入: pattern = "abba", s = "dog cat cat dog"
输出: true

示例 2:

输入:pattern = "abba", s = "dog cat cat fish"
输出: false

示例 3:

输入: pattern = "aaaa", s = "dog cat cat dog"
输出: false
public static boolean wordPattern(String pattern, String s) {
        char[] chars = pattern.toCharArray();
        String[] str = s.split(" ");
        if (chars.length != str.length) return false;
        HashMap<Character, String> map = new HashMap<>();
        for (int i = 0; i < chars.length; i++) {
            if (!map.containsKey(chars[i])) {
                map.put(chars[i], str[i]);
            } else {
                if (!str[i].equals(map.get(chars[i]))) {
                    return false;
                }
            }
        }
        HashMap<String, Character> map2 = new HashMap<>();
        for (int i = 0; i < str.length; i++) {
            if (!map2.containsKey(str[i])) {
                map2.put(str[i], chars[i]);
            } else {
                if (chars[i] != map2.get(str[i])) {
                    return false;
                }
            }
        }
        return true;
    }

    public static boolean wordPattern2(String pattern, String s) {
        //存储字符和字符串存在的位置
        HashMap<Object, Integer> map = new HashMap<>();
        char[] chars = pattern.toCharArray();
        String[] str = s.split(" ");
        if (chars.length != str.length) return false;
        for (int i = 0; i < chars.length; i++){
            if (!Objects.equals(map.put(chars[i], i), map.put(str[i], i))){
                return false;
            }
        }
        return true;
    }

349. 两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的
public int[] intersection(int[] nums1, int[] nums2) {
        HashSet<Integer> set = new HashSet<>();
        int[] longNumArray,shortNumArray;
        if (nums1.length >= nums2.length){
            longNumArray = nums1;
            shortNumArray = nums2;
        }else {
            shortNumArray = nums1;
            longNumArray = nums2;
        }
        for (int num: longNumArray){
            set.add(num);
        }
        HashSet<Integer> set2 = new HashSet<>();
        for (int j : shortNumArray) {
            if (set.contains(j)) {
                set2.add(j);
            }
        }
        int[] r = new int[set2.size()];
        int index =0;
        for (Integer integer : set2) {
            r[index++] = integer;
        }
        return r;
    }

383. 赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true
public boolean canConstruct(String ransomNote, String magazine) {
        HashMap<Character,Integer> magMap = new HashMap<>();
        char[] charArray = magazine.toCharArray();
        for (char c :charArray){
            if (magMap.containsKey(c)){
                magMap.put(c, magMap.get(c) + 1);
            }else {
                magMap.put(c,1);
            }
        }
        for (int i = 0; i < ransomNote.length(); i++){
            if (!magMap.containsKey(ransomNote.charAt(i)) || magMap.get(ransomNote.charAt(i)) == 0){
                return false;
            }
            magMap.put(ransomNote.charAt(i),magMap.get(ransomNote.charAt(i)) - 1);
        }
        return true;
    }
    public boolean canConstruct1(String ransomNote, String magazine) {
        if (magazine.length() < ransomNote.length() ) return false;
        int[] letterCount = new int[26];
        for (int i = 0; i < magazine.length(); i ++){
            letterCount[magazine.charAt(i) - 'a']++;
        }
        for (int i = 0; i < ransomNote.length(); i++){
            if (--letterCount[ransomNote.charAt(i) - 'a'] < 0){
                return false;
            }
        }
        return true;
    }

387. 字符串中的第一个唯一字符

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1

示例 1:

输入: s = "leetcode"
输出: 0

示例 2:

输入: s = "loveleetcode"
输出: 2

示例 3:

输入: s = "aabb"
输出: -1
public static int firstUniqChar(String s) {
        //存储字母对应的索引位,如果重复则置为-1
        HashMap<Character,Integer> map = new HashMap<>();
        for (int i = 0; i < s.length(); i++){
            if (map.containsKey(s.charAt(i))){
                map.put(s.charAt(i), -1);
            }else {
                map.put(s.charAt(i),i);
            }
        }
        int result = Integer.MAX_VALUE;
        //找到不为-1的最小索引位
        for (Map.Entry<Character, Integer> entry : map.entrySet()) {
            if (entry.getValue() != -1 && entry.getValue() < result){
                result = entry.getValue();
            }
        }
        return result==Integer.MAX_VALUE?-1:result;
    }

409. 最长回文串

给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串

在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。

示例 1:

输入:s = "abccccdd"
输出:7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。

示例 2:

输入:s = "a"
输出:1

示例 3:

输入:s = "aaaaaccc"
输出:7
class Solution {
   public int longestPalindrome(String s) {
        int[] count = new int[60];
        for (int i = 0; i < s.length(); i++){
            count[s.charAt(i) - 'A']++;
        }
        int result = 0;
        //是否存在奇数次的字母
        boolean flag = false;
        for (int i =0; i < count.length ; i++){
            if (count[i] >= 1){
                //偶数加上count ,奇数加上count-1
                result += (count[i]&1)==1? count[i]-1:count[i];
                if ((count[i] & 1) == 1){
                    flag = true;
                }
            }
        }
        return flag? result + 1:result;
    }
}

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

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

示例 1:

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

示例 2:

输入:nums = [1,1]
输出:[2]
public List<Integer> findDisappearedNumbers(int[] nums) {
        int [] n = new int[nums.length];
        for (int i = 0; i < nums.length ;i++){
            n[nums[i]]++;
        }
        List<Integer> list = new ArrayList<>();
        for (int i = 1; i < n.length; i++){
            if (n[i] == 0){
                list.add(i);
            }
        }
        return list;
    }
    /*
    原地修改,不要直接覆盖目标位置,这会导致目标位置的元素消失,
    而是在目标位置元素的基础上+nums.length,这就保证了,存在的数 > nums.length
    %运算防止越界
     */
    public List<Integer> findDisappearedNumbers1(int[] nums){
        for (int i = 0; i < nums.length; i++){
            nums[(nums[i] - 1) % nums.length] += nums.length;
        }
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < nums.length; i++){
            if (nums[i] <= nums.length){
                list.add(i+1);
            }
        }
        return list;

    }

575. 分糖果

Alice 有 n 枚糖,其中第 i 枚糖的类型为 candyType[i] 。Alice 注意到她的体重正在增长,所以前去拜访了一位医生。

医生建议 Alice 要少摄入糖分,只吃掉她所有糖的 n / 2 即可(n 是一个偶数)。Alice 非常喜欢这些糖,她想要在遵循医生建议的情况下,尽可能吃到最多不同种类的糖。

给你一个长度为 n 的整数数组 candyType ,返回: Alice 在仅吃掉 n / 2 枚糖的情况下,可以吃到糖的 最多 种类数

示例 1:

输入:candyType = [1,1,2,2,3,3]
输出:3
解释:Alice 只能吃 6 / 2 = 3 枚糖,由于只有 3 种糖,她可以每种吃一枚。

示例 2:

输入:candyType = [1,1,2,3]
输出:2
解释:Alice 只能吃 4 / 2 = 2 枚糖,不管她选择吃的种类是 [1,2]、[1,3] 还是 [2,3],她只能吃到两种不同类的糖。
public int distributeCandies(int[] candyType) {
        int maxEat = candyType.length >> 1;
        Set<Integer> set = new HashSet<>();
        set.add(candyType[0]);
        int res = 1;
        for (int i = 1; i < candyType.length; i++) {
            if (res == maxEat) return maxEat;
            if (!set.contains(candyType[i])){
                res++;
                set.add(candyType[i]);
            }
        }
        return res;
    }

594. 最长和谐子序列

和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1

现在,给你一个整数数组 nums ,请你在所有可能的子序列中找到最长的和谐子序列的长度。

数组的子序列是一个由数组派生出来的序列,它可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。

示例 1:

输入:nums = [1,3,2,2,5,2,3,7]
输出:5
解释:最长的和谐子序列是 [3,2,2,2,3]
public int findLHS(int[] nums) {
        HashMap<Integer,Integer> hashMap = new HashMap<>();
        for (int i = 0 ; i < nums.length ;i ++){
            if (!hashMap.containsKey(nums[i])){
                hashMap.put(nums[i],1);
            }else {
                hashMap.put(nums[i],hashMap.get(nums[i])+1);
            }
        }
        int res = 0;
        for (int num: hashMap.keySet()){
            if (hashMap.containsKey(num + 1)){
                res = Math.max(res,hashMap.get(num) + hashMap.get(num+1));
            }
        }
        return res;
    }

599. 两个列表的最小索引总和

假设 Andy 和 Doris 想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。

你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设答案总是存在。

示例 1:

输入: list1 = ["Shogun", "Tapioca Express", "Burger King", "KFC"],list2 = ["Piatti", "The Grill at Torrey Pines", "Hungry Hunter Steakhouse", "Shogun"]
输出: ["Shogun"]
解释: 他们唯一共同喜爱的餐厅是“Shogun”。
public String[] findRestaurant(String[] list1, String[] list2) {
        HashMap<String,Integer> hashMap = new HashMap<>();
        for (int i = 0; i < list1.length; i++) {
            hashMap.put(list1[i],i);
        }
        int minSum = Integer.MAX_VALUE;
        List<String> res = new ArrayList<>();
        for (int i = 0; i < list2.length; i++) {
            //如果餐厅相同
            if (hashMap.containsKey(list2[i])){
                //计算索引和
                int sum = i + hashMap.get(list2[i]);
                //索引和 <= minSum
                if (sum == minSum ){
                    res.add(list2[i]);
                }else if (sum < minSum){
                    res.clear();
                    res.add(list2[i]);
                    minSum = sum;
                }
            }
        }
        return res.toArray(new String[0]);
    }

49. 字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]
public List<List<String>> groupAnagrams(String[] strs) {
        //key为重新将字符串中字符排序的字符串
        HashMap<String,List<String>> map = new HashMap<>();
        for (String str:strs){
            char[] charArray = str.toCharArray();
            Arrays.sort( charArray);
            String key = Arrays.toString(charArray);
            List<String> stringList = map.getOrDefault(key, new ArrayList<>());
            stringList.add(str);
            map.put(key,stringList);
        }
        return new ArrayList<>(map.values());
    }

七、队列 <-->栈

225. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false
class MyStack {
    public static LinkedList<Integer> list1;
    public static LinkedList<Integer> list2;

    public MyStack() {
        //queue 1保证 存储顺序与stack一致
        list1 = new LinkedList<>();
        list2 = new LinkedList<>();
    }
	//使用两个队列实现栈
    public void push(int x) {
        list2.add(x);
        while (!list1.isEmpty()){
            list2.add(list1.pop());
        }
        LinkedList<Integer> temp = list1;
        list1 = list2;
        list2 = temp;
    }
    //使用一个队列实现栈
    public void pushWithOneList(int x){
        int size = list1.size();
        list1.add(x);
        for(int i = 0; i < size; i++){
            list.add(list.pop());
        }
    }

    public int pop() {
       return list1.pop();
    }

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

    public boolean empty() {
        return list2.isEmpty() && list1.isEmpty();
    }
}

232. 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false
class MyQueue {
    public static Stack<Integer> stack1;
    public static Stack<Integer> stack2;
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    public void push(int x) {
        stack1.push(x);
    }
    public int pop() {
        if (stack2.isEmpty()){
            while (!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
    public int peek() {
        if (stack2.isEmpty()){
            while (!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();
    }
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

八、博弈论

292. Nim 游戏

你和你的朋友,两个人一起玩 Nim 游戏

  • 桌子上有一堆石头。
  • 你们轮流进行自己的回合, 你作为先手
  • 每一回合,轮到的人拿掉 1 - 3 块石头。
  • 拿掉最后一块石头的人就是获胜者。

假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false

示例 1:

输入:n = 4
输出:false 
解释:以下是可能的结果:
1. 移除1颗石头。你的朋友移走了3块石头,包括最后一块。你的朋友赢了。
2. 移除2个石子。你的朋友移走2块石头,包括最后一块。你的朋友赢了。
3.你移走3颗石子。你的朋友移走了最后一块石头。你的朋友赢了。
在所有结果中,你的朋友是赢家。

示例 2:

输入:n = 1
输出:true

示例 3:

输入:n = 2
输出:true

如果为4的倍数,无论选几个,对方都可以使下次还是四的倍数,直到为4,就输了

public boolean canWinNim(int n) {
        return (n & 3) != 0;
    }

九、动态规划

303. 区域和检索 - 数组不可变

给定一个整数数组 nums,处理以下类型的多个查询:

  1. 计算索引 leftright (包含 leftright)之间的 nums 元素的 ,其中 left <= right

实现 NumArray 类:

  • NumArray(int[] nums) 使用数组 nums 初始化对象
  • int sumRange(int i, int j) 返回数组 nums 中索引 leftright 之间的元素的 总和 ,包含 leftright 两点(也就是 nums[left] + nums[left + 1] + ... + nums[right]

示例 1:

输入:
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]

解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
class NumArray {
    public int[] nums;
    public int[] sum;

    public NumArray(int[] nums) {
        this.nums = nums;
        this.sum = new int[nums.length];
        getSum();
    }
    public void getSum(){
        sum[0] = this.nums[0];
        for (int i = 1; i < this.nums.length; i++){
            sum[i] = this.nums[i] + sum[i-1];
        }
    }
public int sumRange(int left, int right) {
        if (left == 0) return sum[right];
        return sum[right] - sum[left-1];
    }
}

509. 斐波那契数

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n)

示例 1:

输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
public int fib(int n) {
        if (n == 1 || n == 0) return n;
        int [] fib = new int[n+1];
        fib[0] = 0;
        fib[1] = 1;
        for (int i = 2; i <= n;i++){
            fib[i] = fib[i-1]+fib[i-2];
        }
        return fib[n];
    }

53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

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

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23
public int maxSubArray(int[] nums) {
		//dp[i] -->以nums[i]为结尾的最大子数组和
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int max = dp[0];
        for (int i = 1; i < nums.length;i++){
        	//如果上一个元素为结尾的最大子数组和 < 0,下次就不用加了
            dp[i] = dp[i-1] > 0? nums[i]+dp[i-1]:nums[i];
            max = Math.max(max,dp[i]);
        }
        return Math.max(max,dp[nums.length-1]);
    }

十、模拟

412. Fizz Buzz

给你一个整数 n ,找出从 1n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer下标从 1 开始)返回结果,其中:

  • answer[i] == "FizzBuzz" 如果 i 同时是 35 的倍数。
  • answer[i] == "Fizz" 如果 i3 的倍数。
  • answer[i] == "Buzz" 如果 i5 的倍数。
  • answer[i] == i (以字符串形式)如果上述条件全不满足。

示例 1:

输入:n = 3
输出:["1","2","Fizz"]

示例 2:

输入:n = 5
输出:["1","2","Fizz","4","Buzz"]
class Solution {
    public List<String> fizzBuzz(int n) {
        List<String> list = new ArrayList<>();
        for (int i = 1; i <= n; i++){
            if (i % 3 == 0) {
                if (i % 5 == 0){
                    list.add("FizzBuzz");
                }else {
                    list.add("Fizz");
                }
            }else if (i % 5 == 0){
                list.add("Buzz");
            }else {
                list.add(String.valueOf(i));
            }
        }
        return list;

    }
}

415. 字符串相加

给定两个字符串形式的非负整数 num1num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

示例 1:

输入:num1 = "11", num2 = "123"
输出:"134"

示例 2:

输入:num1 = "456", num2 = "77"
输出:"533"

示例 3:

输入:num1 = "0", num2 = "0"
输出:"0"
public static String addStrings(String num1, String num2) {
        int cha = Math.abs(num2.length() - num1.length());
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("0".repeat(cha));
        String temp;
        //num1为长,num2为短
        if (num1.length() < num2.length()){
            temp = num2;
            num2 = num1;
            num1 = temp;
        }
        num2 = stringBuilder + num2;
        //进位
        int up = 0;
        stringBuilder = new StringBuilder();
        int curRes = 0;
        for (int i = num1.length()-1; i >=0 ; i--){
            curRes =  num1.charAt(i) - '0'+ num2.charAt(i) - '0' + up;
            up = curRes / 10;
            curRes %= 10;
            stringBuilder.append(curRes);
        }
        if (up != 0){
            stringBuilder.append(up);
        }
        return stringBuilder.reverse().toString();
    }
    public static String addStrings1(String num1, String num2) {
        int num1Index = num1.length() - 1;
        int num2Index = num2.length() - 1;
        StringBuilder stringBuilder = new StringBuilder();
        int up = 0,curRes = 0;
        while (num1Index >= 0 || num2Index >= 0 || up != 0){
            int n1 = num1Index >= 0? num1.charAt(num1Index--) -'0': 0;
            int n2 = num2Index >= 0 ? num2.charAt(num2Index--) -'0':0;
            curRes = n1 + n2 + up;
            up = curRes / 10;
            stringBuilder.append(curRes % 10);
        }
        return stringBuilder.reverse().toString();
    }

434. 字符串中的单词数

统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符。

请注意,你可以假定字符串里不包括任何不可打印的字符。

示例:

输入: "Hello, my name is John"
输出: 5
解释: 这里的单词是指连续的不是空格的字符,所以 "Hello," 算作 1 个单词。
public int countSegments(String s) {
        if (s.isEmpty()) return 0;
        s = s.trim();
        int count = 0;
        String[] strings = s.split(" ");
        for (int i = 0; i < strings.length; i++) {
            if (!strings[i].isEmpty()) {
                count++;
            }
        }
        return count;
    }

482. 密钥格式化

给定一个许可密钥字符串 s,仅由字母、数字字符和破折号组成。字符串由 n 个破折号分成 n + 1 组。你也会得到一个整数 k

我们想要重新格式化字符串 s,使每一组包含 k 个字符,除了第一组,它可以比 k 短,但仍然必须包含至少一个字符。此外,两组之间必须插入破折号,并且应该将所有小写字母转换为大写字母。

返回 重新格式化的许可密钥

示例 1:

输入:S = "5F3Z-2e-9-w", k = 4
输出:"5F3Z-2E9W"
解释:字符串 S 被分成了两个部分,每部分 4 个字符;
     注意,两个额外的破折号需要删掉。

示例 2:

输入:S = "2-5g-3-J", k = 2
输出:"2-5G-3J"
解释:字符串 S 被分成了 3 个部分,按照前面的规则描述,第一部分的字符可以少于给定的数量,其余部分皆为 2 个字符。
public static String licenseKeyFormatting(String s, int k) {
        s = s.toUpperCase();
        StringBuilder stringBuilder = new StringBuilder();
        int strIndex = s.length()-1;
        int n = 0;
        while (strIndex >= 0){
            if (s.charAt(strIndex) != '-'){
                n++;
                stringBuilder.append(s.charAt(strIndex));
                if (n % k == 0){
                    stringBuilder.append('-');
                }
            }
            strIndex--;
            
        }
        if (stringBuilder.length() > 0 && stringBuilder.charAt(stringBuilder.length()-1) == '-'){
            stringBuilder.deleteCharAt(stringBuilder.length()-1);
        }
        return stringBuilder.reverse().toString();
    }

495. 提莫攻击

在《英雄联盟》的世界中,有一个叫 “提莫” 的英雄。他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态。

当提莫攻击艾希,艾希的中毒状态正好持续 duration 秒。

正式地讲,提莫在 t 发起攻击意味着艾希在时间区间 [t, t + duration - 1](含 tt + duration - 1)处于中毒状态。如果提莫在中毒影响结束 再次攻击,中毒状态计时器将会 重置 ,在新的攻击之后,中毒影响将会在 duration 秒后结束。

给你一个 非递减 的整数数组 timeSeries ,其中 timeSeries[i] 表示提莫在 timeSeries[i] 秒时对艾希发起攻击,以及一个表示中毒持续时间的整数 duration

返回艾希处于中毒状态的 秒数。

示例 1:

输入:timeSeries = [1,4], duration = 2
输出:4
解释:提莫攻击对艾希的影响如下:
- 第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
- 第 4 秒,提莫再次攻击艾希,艾希中毒状态又持续 2 秒,即第 4 秒和第 5 秒。
艾希在第 1、2、4、5 秒处于中毒状态,所以总中毒秒数是 4 。

示例 2:

输入:timeSeries = [1,2], duration = 2
输出:3
解释:提莫攻击对艾希的影响如下:
- 第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
- 第 2 秒,提莫再次攻击艾希,并重置中毒计时器,艾希中毒状态需要持续 2 秒,即第 2 秒和第 3 秒。
艾希在第 1、2、3 秒处于中毒状态,所以总中毒秒数是 3 。
public int findPoisonedDuration(int[] timeSeries, int duration) {
        int difference = 0;
        for (int i = 1; i < timeSeries.length; i++){
            //有影响
            if (timeSeries[i] - timeSeries[i-1] < duration){
                difference += duration - (timeSeries[i] - timeSeries[i-1]);
            }

        }
        return timeSeries.length * duration - difference;
    }

500. 键盘行

给你一个字符串数组 words ,只返回可以使用在 美式键盘 同一行的字母打印出来的单词。键盘如下图所示。

美式键盘 中:

  • 第一行由字符 "qwertyuiop" 组成。
  • 第二行由字符 "asdfghjkl" 组成。
  • 第三行由字符 "zxcvbnm" 组成。
image-20230921232515255

示例 1:

输入:words = ["Hello","Alaska","Dad","Peace"]
输出:["Alaska","Dad"]

示例 2:

输入:words = ["omk"]
输出:[]

示例 3:

输入:words = ["adsdf","sfd"]
输出:["adsdf","sfd"]
public String[] findWords(String[] words) {
        String[] ss = new String[]{"qwertyuiop", "asdfghjkl", "zxcvbnm"};
        int[] number = new int[26];
        for (int i = 0; i < ss.length; i++){
            for (char c: ss[i].toCharArray()){
                number[c - 'a'] = i;
            }
        }
        List<String> list = new ArrayList<>();
        out:for (String word : words ){
            //属于该编号
            int t = -1;
            for (char c : word.toCharArray()){
                c = Character.toLowerCase(c);
                //编号
                int no = number[c - 'a'];
                //第一个单词
                if (t == -1){
                    t = no;
                }else if (t != no){
                    continue out;
                }
            }
            list.add(word);
        }
        return list.toArray(new String[list.size()]);
    }

506. 相对名次

给你一个长度为 n 的整数数组 score ,其中 score[i] 是第 i 位运动员在比赛中的得分。所有得分都 互不相同

运动员将根据得分 决定名次 ,其中名次第 1 的运动员得分最高,名次第 2 的运动员得分第 2 高,依此类推。运动员的名次决定了他们的获奖情况:

  • 名次第 1 的运动员获金牌 "Gold Medal"
  • 名次第 2 的运动员获银牌 "Silver Medal"
  • 名次第 3 的运动员获铜牌 "Bronze Medal"
  • 从名次第 4 到第 n 的运动员,只能获得他们的名次编号(即,名次第 x 的运动员获得编号 "x")。

使用长度为 n 的数组 answer 返回获奖,其中 answer[i] 是第 i 位运动员的获奖情况。

示例 1:

输入:score = [5,4,3,2,1]
输出:["Gold Medal","Silver Medal","Bronze Medal","4","5"]
解释:名次为 [1st, 2nd, 3rd, 4th, 5th] 。
public static String[] findRelativeRanks(int[] score) {
        HashMap<Integer,String> map = new HashMap<>();
        List<Integer> list = new ArrayList<>();
        for (int j : score) {
            list.add(j);
        }
        List<Integer> old = new ArrayList<>(list);
        Integer[] array = new Integer[old.size()];
        array = list.toArray(array);

        Arrays.sort(array,Collections.reverseOrder());
        for (int i = 0; i < array.length; i++){
            if ( i > 2){
                break;
            }
            if (i == 0){
                map.put(array[i],"Gold Medal");
            }else if (i == 1){
                map.put(array[i],"Silver Medal");
            }else {
                map.put(array[i],"Bronze Medal");
            }
        }
        for (int i =3; i < array.length; i++){
            map.put(array[i],String.valueOf(i+1));
        }
        String[] result = new String[score.length];
        for (int i = 0; i < old.size(); i++){
            result[i] = map.get(old.get(i));
        }
        return result;
    }

520. 检测大写字母

我们定义,在以下情况时,单词的大写用法是正确的:

  • 全部字母都是大写,比如 "USA"
  • 单词中所有字母都不是大写,比如 "leetcode"
  • 如果单词不只含有一个字母,只有首字母大写, 比如 "Google"

给你一个字符串 word 。如果大写用法正确,返回 true ;否则,返回 false

示例 1:

输入:word = "USA"
输出:true

示例 2:

输入:word = "FlaG"
输出:false
public boolean detectCapitalUse(String word) {
        if (word.length() == 1) return true;
        char firstChar = word.charAt(0);
        if ( firstChar >= 'a' && firstChar <= 'z'){
            return allLow(word);
        }else if (firstChar >= 'A' && firstChar <= 'Z'){
            return allUp(word) || allLow(word.substring(1));
        }
        return false;
    }
    public boolean allUp(String word){
        for (char c: word.toCharArray()){
            if (c > 'Z' || c < 'A'){
                return false;
            }
        }
        return true;
    }
    public boolean allLow(String word){
        for (char c: word.toCharArray()){
            if (c > 'z' || c < 'a'){
                return false;
            }
        }
        return true;
    }

566. 重塑矩阵

在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。

给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 rc ,分别表示想要的重构的矩阵的行数和列数。

重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。

如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

示例 1:

img

输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]
 public int[][] matrixReshape(int[][] mat, int r, int c) {
        if (mat.length * mat[0].length != r * c){
            return mat;
        }
        int[][] newArr = new int[r][c];
        int n = mat[0].length;
        int m = mat.length;
        for (int i = 0; i < m * n; i++){
            newArr[i / c][i % c] = mat[i / n][i % n];
        }
        return newArr;
    }

38. 外观数列

给定一个正整数 n ,输出外观数列的第 n 项。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。

你可以将其视作是由递归公式定义的数字字符串序列:

  • countAndSay(1) = "1"
  • countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。

前五项如下:

1.     1
2.     11
3.     21
4.     1211
5.     111221
第一项是数字 1 
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211"
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221"

描述 一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。

例如,数字字符串 "3322251" 的描述如下图:

img

示例 1:

输入:n = 1
输出:"1"
解释:这是一个基本样例。

示例 2:

输入:n = 4
输出:"1211"
解释:
countAndSay(1) = "1"
countAndSay(2) = 读 "1" = 一 个 1 = "11"
countAndSay(3) = 读 "11" = 二 个 1 = "21"
countAndSay(4) = 读 "21" = 一 个 2 + 一 个 1 = "12" + "11" = "1211"
public static String countAndSay(int n) {
        if (n == 1) return "1";
        String number = "1";
        StringBuilder stringBuilder = null;
        for (int t = 1; t < n; t ++){
            stringBuilder = new StringBuilder();
            int count = 1;
            for (int i = 1; i < number.length(); i++){
                if (number.charAt(i-1) != number.charAt(i)){
                    stringBuilder.append(count);
                    count = 0;
                    stringBuilder.append(number.charAt(i-1));
                }
                count++;
            }
            stringBuilder.append(count);
            stringBuilder.append(number.charAt(number.length()-1));
            number = stringBuilder.toString();

        }
        return stringBuilder.toString();
    }

48. 旋转图像

中等

1.8K

相关企业

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:

img

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

示例 2:

img

输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

第x行数据旋转后变为倒数第x列

[row] [col] = [n-1-col] [row]

public void rotate(int[][] matrix) {
        int l = matrix.length;
        for (int i = 0; i < l/2; i++){
            for (int j = 0; j < (l+1)/2; j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[l-j-1][i];
                matrix[l-j-1][i] = matrix[l-i-1][l-j-1];
                matrix[l-i-1][l-j-1] = matrix[j][l-i-1];
                matrix[j][l-i-1] = temp;
            }
        }
    }

54. 螺旋矩阵

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:

img

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

示例 2:

img

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
public static List<Integer> spiralOrder(int[][] matrix) {
        int curRow = 0;
        int curCol = 0;
        List<Integer> list = new ArrayList<>();
        while (inMatrix(curRow,curCol,matrix)){
             while (inMatrix(curRow,curCol,matrix) ){
                 list.add(matrix[curRow][curCol]);
                 matrix[curRow][curCol++] = 101;
             }
             curCol--;
             curRow++;
             while (inMatrix(curRow,curCol,matrix) ){
                 list.add(matrix[curRow][curCol]);
                 matrix[curRow++][curCol] = 101;
             }
             curRow--;
             curCol--;
            while (inMatrix(curRow,curCol,matrix)){
                list.add(matrix[curRow][curCol]);
                matrix[curRow][curCol--] = 101;
            }
            curCol++;
            curRow--;
            while (inMatrix(curRow,curCol,matrix)){
                list.add(matrix[curRow][curCol]);
                matrix[curRow--][curCol] = 101;
            }
            curRow++;
            curCol++;
        }
        return list;
    }
    public static boolean inMatrix(int row,int col,int[][] matrix){
        return row < matrix.length && row >=0 &&
                col < matrix[0].length && col >= 0 &&
                matrix[row][col] != 101;
    }

十一、排序

414. 第三大的数

给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。

示例 1:

输入:[3, 2, 1]
输出:1
解释:第三大的数是 1 。

示例 2:

输入:[1, 2]
输出:2
解释:第三大的数不存在, 所以返回最大的数 2 。

示例 3:

输入:[2, 2, 3, 1]
输出:1
解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。
此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中排第三大的数为 1 。

方法一:

public int thirdMax(int[] nums) {
        int oneMax = Integer.MIN_VALUE;
        int secondMax = Integer.MIN_VALUE;
        int thirdMax = Integer.MIN_VALUE;
        boolean flag = false;
        for (int i = 0; i < nums.length ;i++){
            oneMax = Math.max(oneMax,nums[i]);

        }
        for (int i = 0; i < nums.length; i++){
            if (nums[i] != oneMax){
                secondMax = Math.max(secondMax,nums[i]);
            }
        }
        for (int i = 0; i  < nums.length; i++){
            if(nums[i] != oneMax && nums[i] != secondMax){
                flag = true;
                thirdMax = Math.max(thirdMax,nums[i]);
            }
        }
        if (!flag){
            return oneMax;
        }
        return thirdMax;
    }

方法二:

image-20230920212052231
public int thirdMax(int[] nums) {
        long a = Long.MIN_VALUE, b = Long.MIN_VALUE, c = Long.MIN_VALUE;
        for (long num : nums) {
            if (num > a) {
                c = b;
                b = a;
                a = num;
            } else if (a > num && num > b) {
                c = b;
                b = num;
            } else if (b > num && num > c) {
                c = num;
            }
        }
        return c == Long.MIN_VALUE ? (int) a : (int) c;
    }

十二、贪心

455. 分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

示例 2:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
class Solution {
     public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        //从最小的开始喂 最小的饼干
        int childIndex = 0;
        int cakeIndex = 0;
        while (childIndex < g.length && cakeIndex < s.length) {
            if (s[cakeIndex] >= g[childIndex]) {
                childIndex++;
            }
            cakeIndex++;
        }
        return childIndex;
    }
}

485. 最大连续 1 的个数

给定一个二进制数组 nums , 计算其中最大连续 1 的个数。

示例 1:

输入:nums = [1,1,0,1,1,1]
输出:3
解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.

示例 2:

输入:nums = [1,0,1,1,0,1]
输出:2
public int findMaxConsecutiveOnes(int[] nums) {
        int sum = 0;
        int result = Integer.MIN_VALUE;
        for(int num:nums){
            if (num == 1){
                sum++;
            }else {
                result = Math.max(result,sum);
                sum = 0;
            }
        }
        return result = Math.max(result,sum);
    

561. 数组拆分

给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1nmin(ai, bi) 总和最大。

返回该 最大总和

示例 1:

输入:nums = [1,4,3,2]
输出:4
解释:所有可能的分法(忽略元素顺序)为:
1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
所以最大总和为 4

示例 2:

输入:nums = [6,2,6,5,1,2]
输出:9
解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9
public int arrayPairSum(int[] nums) {
        int sum = 0;
        Arrays.sort(nums);
        for(int i = 0; i < nums.length; i++){
            //可以优化为i+=2
            if ((i & 1) == 0){
                sum += nums[i];
            }
        }
        return sum;
    }

605. 种花问题

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给你一个整数数组 flowerbed 表示花坛,由若干 01 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false

示例 1:

输入:flowerbed = [1,0,0,0,1], n = 1
输出:true

示例 2:

输入:flowerbed = [1,0,0,0,1], n = 2
输出:false

提示:

  • 1 <= flowerbed.length <= 2 * 104
  • flowerbed[i]01
  • flowerbed 中不存在相邻的两朵花
  • 0 <= n <= flowerbed.length
public static boolean canPlaceFlowers(int[] flowerbed, int n) {
        if (n == 0) return true;
        for (int i = 0; i < flowerbed.length; i++){
            if (flowerbed[i] == 1){
                //下一个位置不能种花
                i++;
                continue;
            }
            //当前位置肯定为0
            if (i == flowerbed.length-1 || flowerbed[i+1] != 1){
                //可以种花,并且下一个位置不能种花
                i++;
                if (--n == 0) return true;
            }
        }
        return false;
    }

11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

示例 1:

image-20231009195359291
输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]
输出:1
 public int maxArea(int[] height) {
        //双指针指向首尾,只有移动短板,水池才有可能变大
        int left = 0,right = height.length-1;
        int maxSum = 0;
        while (left < right){
            maxSum = height[left] < height[right]?
                    Math.max(maxSum,height[left++]*(right -left)):
                    Math.max(maxSum,height[right--]*(right-left));
        }
        return maxSum;
    }

十三、滑动窗口

459. 重复的子字符串

示例 1:

输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。

示例 2:

输入: s = "aba"
输出: false

示例 3:

输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
public class Solution459 {
    /*
    设置一个twoStr = 两倍原字符串 = str + str
     存在: str = s + s  ----->  twoStr = s + [s + s] + s,去头和尾,框中存在str
     不存在:str = a + b + c ------> twoStr = a + b + c + a + b + c,不存在str

     */
    public boolean repeatedSubstringPattern(String s) {
        String twoStr = s + s;
        // substring : [)
        return twoStr.substring(1,twoStr.length()-1).contains(s);
    }
}

551. 学生出勤记录 I

给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

  • 'A':Absent,缺勤
  • 'L':Late,迟到
  • 'P':Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

  • 总出勤 计,学生缺勤('A'严格 少于两天。
  • 学生 不会 存在 连续 3 天或 连续 3 天以上的迟到('L')记录。

如果学生可以获得出勤奖励,返回 true ;否则,返回 false

示例 1:

输入:s = "PPALLP"
输出:true
解释:学生缺勤次数少于 2 次,且不存在 3 天或以上的连续迟到记录。

示例 2:

输入:s = "PPALLL"
输出:false
解释:学生最后三天连续迟到,所以不满足出勤奖励的条件。
public static boolean checkRecord(String s) {
        int absent = 0;
        int late = 0;
        for (char c : s.toCharArray()){
            if (c == 'A'){
                if (absent == 1) return false;
                late = 0;
                absent++;
            }
            //第一个L
            else if (c == 'L'){
                if (late == 2) return  false;
                late++;
            }else{
                late = 0;
            }
        }
        return true;
    }

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

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串
 public static int lengthOfLongestSubstring(String s) {
        if (s.length() == 0) return 0;
        //滑动窗口
        int left = 0,right = 0,maxLength=-1;
        //哈希表
        HashMap<Character,Integer> hashMap = new HashMap<>();
        for (;right < s.length();right++){
            //不重复
            if (hashMap.containsKey(s.charAt(right))){
                //更新最大长度
                maxLength = Math.max(maxLength,right-left);
                //左边界走过的区域,不计算
                left = Math.max(left,hashMap.get(s.charAt(right))+1);
            }
            //更新位置
            hashMap.put(s.charAt(right),right);
        }
        return Math.max(maxLength,right-left);
    }

十四、深度优先搜索

463. 岛屿的周长

给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。

网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

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

示例 1:

image-20230921201339133
输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边

题解

class Solution {
    public int islandPerimeter(int[][] grid) {
        //只有一个大片岛屿,因此找到岛屿,可以直接return
        for (int row = 0; row < grid.length; row++){
            for (int col = 0; col < grid[0].length; col++){
                if (grid[row][col] == 1){
                    return DFS(grid,row,col);
                }
            }
        }
        return 0;
    }
    public int DFS(int[][] grid, int row, int col){
        if (row < 0 || col < 0 || row >= grid.length || col >= grid[0].length){
            return 1;
        }
        //如果不是岛屿
        if (grid[row][col] == 0 ){
            return 1;
        }
        //走过的岛屿,不重复走,标记为非1,为2
        if (grid[row][col] != 1){
            return 0;   
        }
        grid[row][col] = 2;
            return DFS(grid,row-1,col)+
                    DFS(grid,row+1,col)+
                    DFS(grid,row,col-1)+
                    DFS(grid,row,col+1);
    }
}

559. N 叉树的最大深度

给定一个 N 叉树,找到其最大深度。

最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。

示例 1:

img
输入:root = [1,null,3,2,4,null,5,6]
输出:3

示例 2:

img
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:5
 public int maxDepth(Node root) {
        if (root == null) return 0;
        return DFS(root); 
    }
    public int DFS(Node node){
        if (node == null) return 0;
        if (node.children == null){
            return 1;
        }
        //获取每一个孩子的深度
        //获取最大深度
        int max = 0;
        for (Node ch: node.children){
             max = Math.max(max,DFS(ch));
        }
        return max+1;
    

572. 另一棵树的子树

给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例 1:

image-20230927183553652
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        //对于一棵树,需要判断根是否完全相等或子树是否和sub完全相等
        return DFS(root,subRoot);
        //return true;
    }
    public boolean DFS(TreeNode root, TreeNode subRoot){
        if(root == null) {
            return false;
        }
        return check(root,subRoot) || DFS(root.left,subRoot) || DFS(root.right,subRoot);
    }
    /*
    查看以当前节点为根,是否完全相等
    */
   private boolean check(TreeNode root, TreeNode subRoot) {
       //都到了空节点
        if(root == subRoot && root == null){
            return true;
        }
        //只有一个到了
        if (root == null || subRoot == null ||root.val != subRoot.val){
            return false;
        }
        return check(root.left,subRoot.left) && check(root.right,subRoot.right);

    }

543. 二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root

两节点之间路径的 长度 由它们之间边数表示。

示例 1:

img

输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。

示例 2:

输入:root = [1,2]
输出:1
class Solution {
    public int maxDeep = 1;
    public int diameterOfBinaryTree(TreeNode root) {
        getMaxDeep(root);
        return maxDeep-1;
    }
    public int getMaxDeep(TreeNode node){
        if (node == null){
            return 0;
        }
        int leftDeep = getMaxDeep(node.left);
        int rightDeep = getMaxDeep(node.right);
        //自己能用的深度(选一条)
        int curMaxDeep = leftDeep + rightDeep + 1;
        //别人能用的深度(自己为根)
        int toOtherDeep = Math.max(leftDeep,rightDeep)+1;
        //更新maxDeep, 自己能用的肯定大
        maxDeep = Math.max(maxDeep,curMaxDeep);
        //返回该节点的最大深度
        return toOtherDeep;
    }
}

563. 二叉树的坡度

给你一个二叉树的根节点 root ,计算并返回 整个树 的坡度 。

一个树的 节点的坡度 定义即为,该节点左子树的节点之和和右子树节点之和的 差的绝对值 。如果没有左子树的话,左子树的节点之和为 0 ;没有右子树的话也是一样。空结点的坡度是 0 。

整个树 的坡度就是其所有节点的坡度之和。

示例 1:

img

输入:root = [1,2,3]
输出:1
解释:
节点 2 的坡度:|0-0| = 0(没有子节点)
节点 3 的坡度:|0-0| = 0(没有子节点)
节点 1 的坡度:|2-3| = 1(左子树就是左子节点,所以和是 2 ;右子树就是右子节点,所以和是 3 )
坡度总和:0 + 0 + 1 = 1

示例 2:

img

输入:root = [4,2,9,3,5,null,7]
输出:15
解释:
节点 3 的坡度:|0-0| = 0(没有子节点)
节点 5 的坡度:|0-0| = 0(没有子节点)
节点 7 的坡度:|0-0| = 0(没有子节点)
节点 2 的坡度:|3-5| = 2(左子树就是左子节点,所以和是 3 ;右子树就是右子节点,所以和是 5 )
节点 9 的坡度:|0-7| = 7(没有左子树,所以和是 0 ;右子树正好是右子节点,所以和是 7 )
节点 4 的坡度:|(3+5+2)-(9+7)| = |10-16| = 6(左子树值为 3、5 和 2 ,和是 10 ;右子树值为 9 和 7 ,和是 16 )
坡度总和:0 + 0 + 0 + 2 + 7 + 6 = 15
 private int sum = 0;
    public int findTilt(TreeNode root) {
        if (root == null) return 0;
        getSum(root);
        return sum;
    }

    private int getSum(TreeNode root) {
        if (root == null) return 0;
        int left = getSum(root.left);
        int right = getSum(root.right);
        sum += Math.abs(left - right);
        return left + right + root.val;

    }

589. N 叉树的前序遍历

给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历

n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。

示例 1:

img

输入:root = [1,null,3,2,4,null,5,6]
输出:[1,3,5,6,2,4]
public static List<Integer> preorder(Node root) {
        List<Integer> result = new ArrayList<>();
        if (root == null ) return result;
        PreTravel(root,result);
        return result;

    }
    public static void PreTravel(Node root,List<Integer> result) {
        if (root == null) return;
        //加入自己
        result.add(root.val);
        //孩子按顺序
        //获取孩子
        List<Node> children = root.children;
        if (children != null){
            for (Node child : children) {
                PreTravel(child,result);
            }
        }

    }

十五、单调栈

496. 下一个更大元素 I

nums1 中数字 x下一个更大元素 是指 xnums2 中对应位置 右侧第一个x 大的元素。

给你两个 没有重复元素 的数组 nums1nums2 ,下标从 0 开始计数,其中nums1nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素

示例 1:

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。

示例 2:

输入:nums1 = [2,4], nums2 = [1,2,3,4].
输出:[3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。
- 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。

提示:

  • 1 <= nums1.length <= nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 104
  • nums1nums2中所有整数 互不相同
  • nums1 中的所有整数同样出现在 nums2
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        //获取num2的每一位右侧最大值
        int [] bigger = new int[10002];
        int j;
        for(int i = 0; i < nums2.length; i++){
            for (j = i + 1; j < nums2.length; j++){
                if (nums2[j] > nums2[i]){
                    bigger[nums2[i]] = nums2[j];
                    break;
                }
            }
            //没有更大的
            if (j == nums2.length){
                bigger[nums2[i]] = -1;
            }
        }
        int[] result = new int[nums1.length];
        for (int i = 0; i < nums1.length; i++){
            result[i] = bigger[nums1[i]];
        }
        return result;
    }
    /*
    第二种方法:
    使用单调栈保存nums的右侧大的数
     */
    public int[] nextGreaterElement1(int[] nums1, int[] nums2) {
        HashMap<Integer,Integer> map = new HashMap<>();
        Deque<Integer> stack = new ArrayDeque<>();
        //单调栈存储大于nums[i] 的值
        for (int i = nums2.length-1; i >= 0; i--){
            //当前数组值大于或等于栈内的元素,则出栈
            while (!stack.isEmpty() && nums2[i] >= stack.peek()){
                stack.pop();
            }
            map.put(nums2[i],stack.isEmpty()?-1:stack.peek());
            stack.push(nums1[i]);
        }
        for (int i = 0 ; i < nums1.length; i++){
            nums1[i] = map.get(nums1[i]);
        }
        return nums1;
    }

十六、Mirrors遍历

Morris遍历细节

假设来到当前节点cur,开始时cur来到头节点位置

1)如果cur没有左孩子,cur向右移动(cur = cur.right)

2)如果cur有左孩子,找到左子树上最右的节点mostRight:

​ a.如果mostRight的右指针指向空,让其指向cur, 然后cur向左移动(cur = cur.left)

​ b.如果mostRight的右指针指向cur,让其指向null, 然后cur向右移动(cur = cur.right)

cur为空时遍历停止

public static void morrisTravel(Node head){
    if (head == null){
        return;
    }
    Node cur = head;
    Node mostRightNode;
    while (cur != null){
        mostRightNode = cur.left;
        //cur有左孩子
        if (mostRightNode != null){
            //找到左子树的最右节点,并且保证mostRight不会移动回cur
            while (mostRightNode.right != null && mostRightNode.right != cur){
                mostRightNode = mostRightNode.right;
            }
            //看mostRight是否有right
            if (mostRightNode.right == null){
                //没有right,则添加right指向cur
                mostRightNode.right = cur;
                //并且cur向左遍历
                cur = cur.left;
                //继续这一循环
                continue;
            }
            //指向了cur
            else {
                //断开连接
                mostRightNode.right = null;
                //cur向右移动
            }
        }
        //cur没有左孩子,cur向右移动
        cur = cur.right;
    }
}

先序遍历

//先序:能回来两次的节点,第一次打印,第二次不打印
//     只能到一次的节点,直接打印
public static void morrisPreTravel(Node head){
    if (head == null){
        return;
    }
    Node cur = head;
    Node mostRightNode;
    while (cur != null){
        mostRightNode = cur.left;
        //cur有左孩子
        //进入if:能回cur两次的
        if (mostRightNode != null){
            //找到左子树的最右节点,并且保证mostRight不会移动回cur
            while (mostRightNode.right != null && mostRightNode.right != cur){
                mostRightNode = mostRightNode.right;
            }
            //看mostRight是否有right
            if (mostRightNode.right == null){
                //第一次来到当前节点
                System.out.print(cur.value+" ");
                //没有right,则添加right指向cur
                mostRightNode.right = cur;
                //并且cur向左遍历
                cur = cur.left;
                //继续这一循环
                continue;
            }
            //指向了cur
            else {
                //第二次来到cur
                //不打印
                //断开连接
                mostRightNode.right = null;
                //cur向右移动
            }
        }
        //没有左子树的,走到else
        else {
            System.out.print(cur.value+" ");
        }
        //cur没有左孩子,cur向右移动
        cur = cur.right;
    }
}

中序

//中序:能回来两次的节点,第一次不打印,第二次打印
//     只能到一次的节点,直接打印
public static void morrisMediaTravel(Node head){
    if (head == null){
        return;
    }
    Node cur = head;
    Node mostRightNode;
    while (cur != null){
        mostRightNode = cur.left;
        //cur有左孩子
        //进入if:能回cur两次的
        if (mostRightNode != null){
            //找到左子树的最右节点,并且保证mostRight不会移动回cur
            while (mostRightNode.right != null && mostRightNode.right != cur){
                mostRightNode = mostRightNode.right;
            }
            //看mostRight是否有right
            if (mostRightNode.right == null){
                //没有right,则添加right指向cur
                mostRightNode.right = cur;
                //并且cur向左遍历
                cur = cur.left;
                //继续这一循环
                continue;
            }
            //指向了cur
            else {
                //第二次来到cur
                //不打印
                //断开连接
                mostRightNode.right = null;
                //cur向右移动
            }
        }
        System.out.print(cur.value+" ");
        //cur没有左孩子,cur向右移动
        cur = cur.right;
    }
}

是否为线索二叉树

可以根据中序遍历改编

//中序:能回来两次的节点,第一次不打印,第二次打印
//     只能到一次的节点,直接打印
public static boolean morrisMediaTravel(Node head){
    if (head == null){
        return true ;
    }
    Node cur = head;
    Node mostRightNode;
    int preNodeValue = Integer.MIN_VALUE;
    while (cur != null){
        mostRightNode = cur.left;
        //cur有左孩子
        //进入if:能回cur两次的
        if (mostRightNode != null){
            //找到左子树的最右节点,并且保证mostRight不会移动回cur
            while (mostRightNode.right != null && mostRightNode.right != cur){
                mostRightNode = mostRightNode.right;
            }
            //看mostRight是否有right
            if (mostRightNode.right == null){
                //没有right,则添加right指向cur
                mostRightNode.right = cur;
                //并且cur向左遍历
                cur = cur.left;
                //继续这一循环
                continue;
            }
            //指向了cur
            else {
                //第二次来到cur
                //不打印
                //断开连接
                mostRightNode.right = null;
                //cur向右移动
            }
        }
        //System.out.print(cur.value+" ");
        if (cur.value < preNodeValue){
            return false;
        }
        preNodeValue = cur.value;
        //cur没有左孩子,cur向右移动
        cur = cur.right;
    }
    return true;
}

501. 二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

示例 1:

img

输入:root = [1,null,2,2]
输出:[2]

示例 2:

输入:root = [0]
输出:[0]
/**
     * Mirrors遍历
     * 中序遍历是递增的,
     *          当node.value不变时:采用滑动窗口,不断扩大count值,
     *          当node.value改变时,preVal更新,count置1
     *          如果在这个过程中,count >maxCount,更新maxCount,清空list,preVal加入list
     *                          count == maxCount,preVal加入list
     */
public static int[] findMode(TreeNode root) {
        TreeNode cur = root;
        TreeNode mostRight = null;
        int preVal = cur.val;
        int count=0,maxCount=0;
        List<Integer> list = new ArrayList<>();
        while (cur != null){
            mostRight = cur.left;
            if (mostRight != null ){
                while (mostRight.right != null && mostRight.right != cur){
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null){
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }else {
                    mostRight.right = null;
                }
            }
            if (cur.val == preVal){
                count++;
            }else{
                preVal = cur.val;
                count = 1;
            }
            if (count == maxCount){
                list.add(preVal);
            }else if (count > maxCount){
                maxCount = count;
                list.clear();;
                list.add(preVal);
            }
            cur = cur.right;
        }
        int[] result = new int[list.size()];
        for (int i = 0; i < result.length ; i++){
            result[i] = list.get(i);
        }
        return result;
    }

530. 二叉搜索树的最小绝对差

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值

差值是一个正数,其数值等于两值之差的绝对值。

示例 1:

image-20230922230105973
输入:root = [4,2,6,1,3]
输出:1
public int getMinimumDifference(TreeNode root) {
        TreeNode cur = root;
        TreeNode mostRight = null;
        int result = Integer.MAX_VALUE;
        int preVal = Integer.MAX_VALUE;
        while (cur != null){
            mostRight = cur.left;
            if (mostRight != null){
                while (mostRight.right != null && mostRight.right != cur){
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null){
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }else {
                    mostRight.right = null;
                }
            }
            result = Math.min(Math.abs(cur.val - preVal),result);
            preVal = cur.val;
            cur = cur.right;
        }
        return result;
    }

十七、脑筋急转弯

521. 最长特殊序列 Ⅰ

给你两个字符串 ab,请返回 这两个字符串中 最长的特殊序列 的长度。如果不存在,则返回 -1

「最长特殊序列」 定义如下:该序列为 某字符串独有的最长子序列(即不能是其他字符串的子序列)

字符串 s 的子序列是在从 s 中删除任意数量的字符后可以获得的字符串。

  • 例如,"abc""aebdc" 的子序列,因为删除 "a***e***b***d\***c" 中斜体加粗的字符可以得到 "abc""aebdc" 的子序列还包括 "aebdc""aeb""" (空字符串)。

示例 1:

输入: a = "aba", b = "cdc"
输出: 3
解释: 最长特殊序列可为 "aba" (或 "cdc"),两者均为自身的子序列且不是对方的子序列。

示例 2:

输入:a = "aaa", b = "bbb"
输出:3
解释: 最长特殊序列是 "aaa" 和 "bbb" 。

示例 3:

输入:a = "aaa", b = "aaa"
输出:-1
解释: 字符串 a 的每个子序列也是字符串 b 的每个子序列。同样,字符串 b 的每个子序列也是字符串 a 的子序列。
ublic int findLUSlength(String a, String b) {
        return !a.equals(b)?Math.max(a.length(),b.length()):-1;
    }

十八、Manacher

Manacher算法

/**
 * @Author: 郜宇博
 */
public class Manacher {
    public static void main(String[] args) {
        String str1 = "abc1234321ab";
        System.out.println(maxLcpsLength(str1));
    }

    /**
     * 最长回文子串
     * 变量:c:导致R右扩的中心点,R:回文右边界 i:当前点, i':i关于c的对称点
     *      p[]:可以忽略判断的点个数
     * 分为两种大情况
     * 1.i在R外,那么就正常向两边扩(不确定回文数)
     * 2.i在R内,有分为三种情况
     *      2.1。 当i'的回文区域在[L,R]内,可以忽略的点个数为i'的回文半径(已经确定该点回文数)
     *      2.2。 当i'的回文区域在[L,R]外,也就是超过了L,可以忽略的点个数为R-i(已经确定该点回文数)
     *      2.3.  当i'的回文区域在[L,R]上,也就是压线,可以忽略的点个数为R-i(不确定回文数,需要判断下一个位置)
     * 当走完数组后,数组内最大值就是最大的回文半径
     * 因为加入了特殊字符如:#1#2#2#1#
     * 所以回文长度为 半径-1
     *
     */
    public static int maxLcpsLength(String str){
        if (str == null || str.length() == 0) {
            return 0;
        }
        //添加特殊符号后的数组
        char[] charArr = manacherString(str);
        //半径长度(包括自己)
        int[] pArr = new int[charArr.length];
        int max = Integer.MIN_VALUE;
        //导致右边界的中心点
        int center = -1;
        //右边界
        int right = -1;
        for (int i = 0; i < charArr.length; i++) {
            //半径长度, 也就是获取可以忽略的点数+1
            pArr[i] = right > i ? Math.min(pArr[2*center-i],right-i):1;
            //虽然有的情况已经确定了回文数,但是为了减少代码量,因此统一一个扩张接口。
            while (i + pArr[i] <charArr.length && i-pArr[i] >= 0){
                //判断两边是否相等
                if (charArr[i + pArr[i] ] == charArr[i-pArr[i] ]){
                    pArr[i]++;
                }
                else {
                    break;
                }
            }
            //扩张后,查看是否超过了R,超过则更新,并保存c
            if (i + pArr[i] > right){
                right = i + pArr[i];
                center = i;
            }
            //更新max值
            max = Math.max(max,pArr[i]);
        }
        System.out.println(Arrays.toString(pArr));
        return max-1;

    }

    private static char[] manacherString(String str) {
        char[] charArr = str.toCharArray();
        char[] res = new char[str.length() * 2 + 1];
        int index = 0;
        for (int i = 0; i != res.length; i++) {
            res[i] = (i & 1) == 0 ? '#' : charArr[index++];
        }
        return res;
    }
}

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

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

示例 2:

输入:s = "cbbd"
输出:"bb"
class Solution {
   public static String longestPalindrome(String s) {
        char[] charArr = manacherString(s);
        int Right = -1,Centre = -1,maxLength = -1,rc = 0,end,start;
        int[] RLength = new int[charArr.length];
        for (int i = 0; i < charArr.length; i++) {
            RLength[i] = Right > i?Math.min(Right-i,RLength[2 * Centre - i]):1;
            while (i + RLength[i] < charArr.length && i - RLength[i] >= 0){
                if (charArr[i + RLength[i]] == charArr[i - RLength[i]]){
                    RLength[i]++;
                }else {
                    break;
                }
            }
            if (i + RLength[i] > Right){
                Right = i + RLength[i];
                Centre = i;
            }
            if (RLength[i] > maxLength){
                rc = i;
                maxLength = RLength[i];
            }
        }
        StringBuilder stringBuilder = new StringBuilder();
        start = rc /2 - ((maxLength-1)/2);
        end = ((maxLength - 1)& 1) == 0? rc/2 + ((maxLength-1)/2):rc/2 + ((maxLength-1)/2)+1;
        for (int i = start; i < end; i++){
            stringBuilder.append(s.charAt(i));
        }
        return stringBuilder.toString();

    }
    public static char[] manacherString(String s) {
        char[] charArr = new char[(s.length() << 1) + 1];
        char[] sArr = s.toCharArray();
        int index = 0;
        for (int i = 0;  i < charArr.length; i++){
            charArr[i] = (i & 1) == 0?'#':sArr[index++];
        }
        return charArr;
    }
}

十九、回溯

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

img

剪枝提速

根据上面画树形图的经验,如果 target 减去一个数得到负数,那么减去一个更大的树依然是负数,同样搜索不到结果。基于这个想法,我们可以对输入数组进行排序,添加相关逻辑达到进一步剪枝的目的;

排序是为了提高搜索速度,对于解决这个问题来说非必要。但是搜索问题一般复杂度较高,能剪枝就尽量剪枝。实际工作中如果遇到两种方案拿捏不准的情况,都试一下。

 public static List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Deque<Integer> path = new ArrayDeque<>();
        Arrays.sort(candidates);
        partition(result, candidates, 0, target, path);
        return result;
    }
    public static void partition(List<List<Integer>> result,int[] candidates,int curIndex,int less,Deque<Integer> path){
        if (less < 0) return;
        if (less == 0) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = curIndex; i < candidates.length; i++){
            //剪枝操作
            if (candidates[i] > less) break;
            //加入元素
            path.addLast(candidates[i]);
            partition(result,candidates,i,less-candidates[i],path);
            //重置状态
            path.removeLast();
        }
    }

40. 组合总和 II

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

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

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

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
 public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(candidates);
        dfs(result,target,0,candidates,new ArrayList<>());
        return result;
    }
    public void dfs(List<List<Integer>> result,int less,int curIndex,int[] candidates,List<Integer> part){
        if (less < 0 ) return;
        if (less == 0){
            result.add(new ArrayList<>(part));
            return;
        }
        for (int i = curIndex;i < candidates.length; i++){
            if(less < candidates[i]) break; // 后面大的数字就不用算了
            //如果上一个要加入的数字和当前数字一样(1,1,6)这个子结果不会跳过,是因为1 和 1 不是同一层的,
            //candidates[i-1] == candidates[i]肯定在同一个位置(前面的排序确定了相同元素挨着)
            if(i > curIndex && candidates[i-1] == candidates[i]) continue;
            part.add(candidates[i]);
            dfs(result,less-candidates[i],i+1,candidates,part);
            part.remove(part.size()-1);
        }
    }

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]
public static List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        //是否使用过
        boolean[] isUse = new boolean[nums.length];
        dfs(result,path,0,nums,isUse);
        return result;
    }
    public static void dfs(List<List<Integer>> result,List<Integer> path,Integer curIndex,int[] nums,boolean[] isUse){
        if (curIndex == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++){
            if (!isUse[i]){
                path.add(nums[i]);
                isUse[i] = true;
                dfs(result,path,curIndex+1,nums,isUse);
                //使用完毕
                isUse[i] = false;
                path.remove(path.size()-1);
            }
        }
    }

47. 全排列 II

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> result =  new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        boolean[] isUse = new boolean[nums.length];
        Arrays.sort(nums);
        dfs(result,path,isUse,nums,0);
        return result;
    }

    public void dfs(List<List<Integer>> result, List<Integer> path,boolean[] isUse,int[] nums, int curIndex){
        if(curIndex == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int i = 0;i < nums.length; i++){
            
            if(!isUse[i]){
                //同一层的兄弟节点同一位置元素不能相同,并且保证这个元素确实被加入过,那么这次就不加入了
                if(i > 0 && nums[i-1] == nums[i] && !isUse[i-1]) continue;
                path.add(nums[i]);
                isUse[i] = true;
                dfs(result,path,isUse,nums,curIndex+1);
                path.remove(path.size()-1);
                isUse[i] = false;
            }
        }
    }
}
posted @ 2023-10-19 20:20  橡皮筋儿  阅读(64)  评论(0编辑  收藏  举报