【leetcode】程序员面试金典

本文深入探讨了位操作和递归在编程中的广泛应用,包括字符判断、字符重排、URL编码、回文判断、计算奇数个单词数、链表操作、矩阵旋转、零矩阵标记、字符串轮转、删除链表节点、二叉搜索树相关操作、二进制数转换、位运算操作、数组的子集生成、汉诺塔问题和颜色填充等。通过实例解析,展示了位运算和递归如何高效地解决各种问题。
摘要由CSDN通过智能技术生成

文章目录

判定字符是否唯一

实现一个算法,确定一个字符串 s 的所有字符是否全都不同。

示例 1:
输入: s = “leetcode”
输出: false

示例 2:
输入: s = “abc”
输出: true

boolean数组

class Solution {
    public boolean isUnique(String astr) {
        boolean[] exist = new boolean[26];
        char[] charArray = astr.toCharArray();
        for (char c : charArray) {
            if (exist[c - 'a'])
                return false;
            exist[c - 'a'] = true;
        }
        return true;
    }
}

位运算

class Solution {
    public boolean isUnique(String astr) {
        int mask = 0;
        char[] charArray = astr.toCharArray();
        for (char c : charArray) {
            int i = 1 << (c - 'a');
            if ((mask & i) != 0)
                return false;
            mask |= i;
        }
        return true;
    }
}

判定是否互为字符重排

给定两个字符串 s1 和 s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。

示例 1:
输入: s1 = “abc”, s2 = “bca”
输出: true

示例 2:
输入: s1 = “abc”, s2 = “bad”
输出: false

记录字符个数

class Solution {
    public boolean CheckPermutation(String s1, String s2) {
        if (s1.length() != s2.length())
            return false;
            
        int[] count = new int[128];
        char[] charArray1 = s1.toCharArray();
        char[] charArray2 = s2.toCharArray();
        for (char c : charArray1)
            count[c]++;
        for (char c : charArray2) {
            if (count[c]-- == 0)
                return false;
        }
        return true;
    }
}

URL化

URL化。编写一种方法,将字符串中的空格全部替换为%20。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的“真实”长度。(注:用Java实现的话,请使用字符数组实现,以便直接在数组上操作。)

示例 1:
输入:"Mr John Smith ", 13
输出:“Mr%20John%20Smith”

示例 2:
输入:" “, 5
输出:”%20%20%20%20%20"

倒序遍历

class Solution {
    public String replaceSpaces(String S, int length) {
        char[] charArray = S.toCharArray();
        int len = charArray.length;
        int cur = len - 1;
        for (int i = length - 1; i >= 0; i--) {
            if (charArray[i] == ' ') {
                charArray[cur--] = '0';
                charArray[cur--] = '2';
                charArray[cur--] = '%';
            } else {
                charArray[cur--] = charArray[i];
            }
        }
        return new String(charArray, cur + 1, len - cur - 1);
    }
}

回文排列

给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。
回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。
回文串不一定是字典当中的单词。

示例1:
输入:“tactcoa”
输出:true(排列有"tacocat"、“atcocta”,等等)

计算奇数个的单词数量

class Solution {
    public boolean canPermutePalindrome(String s) {
        int count = 0;
        int[] map = new int[128];
        char[] charArray = s.toCharArray();
        for (char c : charArray) {
            if (map[c]++ % 2 == 1)
                count--;
            else
                count++;
        }
        return count < 2;
    }
}

位运算

class Solution {
    public boolean canPermutePalindrome(String s) {
        long highBitmap = 0, lowBitmap = 0;
        for (char ch : s.toCharArray()) {
            if (ch >= 64) 
                highBitmap ^= 1L << ch - 64;
            else
                lowBitmap ^= 1L << ch;
        }
        return Long.bitCount(highBitmap) + Long.bitCount(lowBitmap) < 2;
    }
}

一次编辑

字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。

示例 1:
输入:
first = “pale”
second = “ple”
输出: True

示例 2:
输入:
first = “pales”
second = “pal”
输出: False

双指针

class Solution {
    public boolean oneEditAway(String first, String second) {
        if (first.equals(second))
            return true;
        if (Math.abs(first.length() - second.length()) > 1)
            return false;

        int len1 = first.length(), len2 = second.length();
        int s1 = 0, e1 = len1 - 1;
        int s2 = 0, e2 = len2 - 1;
        while (s1 < len1 && s2 < len2 && first.charAt(s1) == second.charAt(s2)) {
            s1++; s2++;
        }
        while (e1 >= s1 && e2 >= s2 && first.charAt(e1) == second.charAt(e2)) {
            e1--; e2--;
        }
        return e1 - s1 < 1 && e2 - s2 < 1;
    }
}

字符串压缩

字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a至z)。

示例1:
输入:“aabcccccaaa”
输出:“a2b1c5a3”

示例2:
输入:“abbccd”
输出:“abbccd”
解释:“abbccd"压缩后为"a1b2c2d1”,比原字符串长度更长。

遍历计数

class Solution {
    public String compressString(String S) {
        StringBuilder sb = new StringBuilder();
        char[] charArray = S.toCharArray();
        int len = charArray.length;
        for (int i = 0; i < len; i++) {
            int count = 1;
            while (i + 1 < len && charArray[i] == charArray[i + 1]) {
                i++; count++;
            }
            sb.append(charArray[i]).append(count);
        }
        return sb.length() < len ? sb.toString() : S;
    }
}

旋转矩阵

给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?

示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]

示例 2:
给定 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]
]

模拟

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

水平翻转+主对角线翻转

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

零矩阵

编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。

示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]

示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]

标记法

class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length;
        if (m == 0)
            return;
        int n = matrix[0].length;

        boolean row = false, col = false;
        for (int i = 0; i < n; i++) {
            if (matrix[0][i] == 0) {
                row = true;
                break;
            }
        }
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                col = true;
                break;
            }
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[0][j] = 0;
                    matrix[i][0] = 0;
                }
            }
        }

        for (int i = 1; i < m; i++) {
            if (matrix[i][0] == 0) {
                for (int j = 1; j < n; j++)
                    matrix[i][j] = 0;
            }
        }
        for (int j = 1; j < n; j++) {
            if (matrix[0][j] == 0) {
                for (int i = 1; i < m; i++)
                    matrix[i][j] = 0;
            }
        }
        if (row) {
            for (int j = 0; j < n; j++)
                matrix[0][j] = 0;
        }
        if (col) {
            for (int i = 0; i < m; i++)
                matrix[i][0] = 0;
        }
    }
}

字符串轮转

字符串轮转。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成(比如,waterbottle是erbottlewat旋转后的字符串)。

示例1:
输入:s1 = “waterbottle”, s2 = “erbottlewat”
输出:True

示例2:
输入:s1 = “aa”, s2 = “aba”
输出:False

拼接+查找

class Solution {
    public boolean isFlipedString(String s1, String s2) {
        return s1.length() == s2.length() && (s1 + s1).indexOf(s2) != -1;
    }
}

移除重复节点

编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。

示例1:
输入:[1, 2, 3, 3, 2, 1]
输出:[1, 2, 3]

示例2:
输入:[1, 1, 1, 1, 2]
输出:[1, 2]

哈希表

class Solution {
    public ListNode removeDuplicateNodes(ListNode head) {
        Set<Integer> set = new HashSet<>();
        ListNode cur = head, pre = head;
        while (cur != null) {
            if (set.contains(cur.val)) {
                pre.next = cur.next;
                cur = cur.next;
                continue;
            }
            set.add(cur.val);
            pre = cur;
            cur = cur.next;
        }
        return head;
    }
}

返回倒数第 k 个节点

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
注意:本题相对原题稍作改动

示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4

双指针

class Solution {
    public int kthToLast(ListNode head, int k) {
        ListNode left = head, right = head;
        while (k-- > 0)
            right = right.next;
        while (right != null) {
            left = left.next;
            right = right.next;
        }
        return left.val;
    }
}

删除中间节点

实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。

示例:
输入:单向链表a->b->c->d->e->f中的节点c
结果:不返回任何数据,但该链表变为a->b->d->e->f

直接替换

class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

分割链表

编写程序以 x 为基准分割链表,使得所有小于 x 的节点排在大于或等于 x 的节点之前。如果链表中包含 x,x 只需出现在小于 x 的元素之后(如下所示)。分割元素 x 只需处于“右半部分”即可,其不需要被置于左右两部分之间。

示例:
输入: head = 3->5->8->5->10->2->1, x = 5
输出: 3->1->2->10->5->5->8

两个链表

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode small = new ListNode(0), large = new ListNode(0), cur = head;
        ListNode smallHead = small, largeHead = large;
        while (cur != null) {
            if (cur.val < x) {
                small.next = cur;
                small = cur;
            } else {
                large.next = cur;
                large = cur;
            }
            cur = cur.next;   
        }
        small.next = largeHead.next;
        large.next = null;
        return smallHead.next;
    }
}

链表求和

给定两个用链表表示的整数,每个节点包含一个数位。
这些数位是反向存放的,也就是个位排在链表首部。
编写函数对这两个整数求和,并用链表形式返回结果。

示例:
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912
进阶:思考一下,假设这些数位是正向存放的,又该如何解决呢?

示例:
输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295
输出:9 -> 1 -> 2,即912

迭代

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(0), prev = head;
        int carry = 0;
        while (l1 != null || l2 != null || carry != 0) {
            int sum = (l1 != null ? l1.val : 0) + (l2 != null ? l2.val : 0) + carry;
            carry = sum / 10;
            prev.next = new ListNode(sum % 10);
            prev = prev.next;
            l1 = l1 != null ? l1.next : l1;
            l2 = l2 != null ? l2.next : l2;
        }
        return head.next;
    }
}

回文链表

编写一个函数,检查输入的链表是否是回文的。

示例 1:
输入: 1->2
输出: false

示例 2:
输入: 1->2->2->1
输出: true

双指针

class Solution {
    public boolean isPalindrome(ListNode head) {
        int size = 0;
        ListNode cur = head;
        while (cur != null) {
            cur = cur.next;
            size++;
        }
        
        cur = head;
        ListNode pre = null, next = head;
        int k = (size + 1) / 2;
        while (k-- > 0) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        if (size % 2 == 1)
            pre = pre.next;
        while (cur != null) {
            if (cur.val != pre.val)
                return false;
            cur = cur.next;
            pre = pre.next;
        }
        return true;
    }
}

链表相交

给定两个(单向)链表,判定它们是否相交并返回交点。请注意相交的定义基于节点的引用,而不是基于节点的值。换句话说,如果一个链表的第k个节点与另一个链表的第j个节点是同一节点(引用完全相同),则这两个链表相交。

示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

双指针

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode a = headA, b = headB;
        while (a != b) {
            a = a == null ? headB : a.next;
            b = b == null ? headA : b.next;
        }
        return a;
    }
}

环路检测

给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

示例 1:
在这里插入图片描述
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
在这里插入图片描述
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
在这里插入图片描述
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

快慢指针

在这里插入图片描述
slow和fast指针在b点处相遇。此时,slow走过的路程为 a + b a+b a+b,fast走过的路程为 a + b + n ( b + c ) a+b+n(b+c) a+b+n(b+c)
因为 2 ( a + b ) = a + b + n ( b + c ) 2(a+b)=a+b+n(b+c) 2(a+b)=a+b+n(b+c) a = c + ( n − 1 ) ( b + c ) a= c+(n-1)(b+c) a=c+(n1)(b+c),所以此时从head开始遍历的指针与slow指针会在入口相遇。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head, fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null)
                fast = fast.next.next;
            else
                break;
            if (slow == fast) {
                ListNode cur = head;
                while (cur != slow) {
                    cur = cur.next;
                    slow = slow.next;
                }
                return cur;
            }
        }
        return null;
    }
}

三合一

三合一。描述如何只用一个数组来实现三个栈。
你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标,value表示压入的值。
构造函数会传入一个stackSize参数,代表每个栈的大小。

示例1:
输入:
[“TripleInOne”, “push”, “push”, “pop”, “pop”, “pop”, “isEmpty”]
[[1], [0, 1], [0, 2], [0], [0], [0], [0]]
输出:
[null, null, null, 1, -1, -1, true]
说明:当栈为空时pop, peek返回-1,当栈满时push不压入元素。

示例2:
输入:
[“TripleInOne”, “push”, “push”, “push”, “pop”, “pop”, “pop”, “peek”]
[[2], [0, 1], [0, 2], [0, 3], [0], [0], [0], [0]]
输出:
[null, null, null, null, 2, 1, -1, -1]

一维数组

class TripleInOne {
    private int[] data;
    private int[] index;
    private int capacity;
    public TripleInOne(int stackSize) {
        data = new int[3 * stackSize];
        index = new int[]{0, stackSize, 2 * stackSize};
        capacity = stackSize;
    }   
    
    public void push(int stackNum, int value) {
        if (index[stackNum] >= (stackNum + 1) * capacity)
            return;
        data[index[stackNum]++] = value;
    }
    
    public int pop(int stackNum) {
        return isEmpty(stackNum) ? -1 : data[--index[stackNum]];
    }
    
    public int peek(int stackNum) {
        return isEmpty(stackNum) ? -1 : data[index[stackNum] - 1];
    }
    
    public boolean isEmpty(int stackNum) {
        return index[stackNum] == stackNum * capacity;
    }
}

二维数组

class TripleInOne {
    private int[][] data;
    private int[] index;
    private int capacity;
    public TripleInOne(int stackSize) {
        data = new int[3][stackSize];
        index = new int[]{0, 0, 0};
        capacity = stackSize;
    }   
    
    public void push(int stackNum, int value) {
        if (index[stackNum] == capacity)
            return;
        data[stackNum][index[stackNum]++] = value;
    }
    
    public int pop(int stackNum) {
        return !isEmpty(stackNum) ? data[stackNum][--index[stackNum]] : -1;
    }
    
    public int peek(int stackNum) {
        return !isEmpty(stackNum) ? data[stackNum][index[stackNum] - 1] : -1;
    }
    
    public boolean isEmpty(int stackNum) {
        return index[stackNum] == 0;
    }
}

栈的最小值

请设计一个栈,除了常规栈支持的pop与push函数以外,还支持min函数,该函数返回栈元素中的最小值。执行push、pop和min操作的时间复杂度必须为O(1)。

示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

辅助栈

class MinStack {
    private Deque<int[]> stack;

    public MinStack() {
        stack = new LinkedList<int[]>();
    }
    
    public void push(int x) {
        int min = (stack.isEmpty() || x < stack.peek()[1]) ? x : stack.peek()[1];
        stack.push(new int[]{x, min});
    }
    
    public void pop() {
        stack.pop();
    }
    
    public int top() {
        return stack.peek()[0];
    }
    
    public int getMin() {
        return stack.peek()[1];
    }
}

化栈为队

实现一个MyQueue类,该类用两个栈来实现一个队列。

示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false

入栈+出栈

class MyQueue {
    private Deque<Integer> inStack;
    private Deque<Integer> outStack;

    public MyQueue() {
        inStack = new LinkedList<>();
        outStack = new LinkedList<>();
    }
    
    public void push(int x) {
        inStack.push(x);
    }
    
    public int pop() {
        if (outStack.isEmpty()) 
            transmit();
        return outStack.pop();
    }
    
    public int peek() {
        if (outStack.isEmpty()) 
            transmit();
        return outStack.peek();
    }
    
    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

    private void transmit() {
        while (!inStack.isEmpty())
            outStack.push(inStack.pop());
    }
}

动物收容所

动物收容所。有家动物收容所只收容狗与猫,且严格遵守“先进先出”的原则。在收养该收容所的动物时,收养人只能收养所有动物中“最老”(由其进入收容所的时间长短而定)的动物,或者可以挑选猫或狗(同时必须收养此类动物中“最老”的)。换言之,收养人不能自由挑选想收养的对象。请创建适用于这个系统的数据结构,实现各种操作方法,比如enqueue、dequeueAny、dequeueDog和dequeueCat。允许使用Java内置的LinkedList数据结构。

  • enqueue方法有一个animal参数,animal[0]代表动物编号,animal[1]代表动物种类,其中 0 代表猫,1 代表狗。
  • dequeue*方法返回一个列表[动物编号, 动物种类],若没有可以收养的动物,则返回[-1,-1]。

示例1:
输入:
[“AnimalShelf”, “enqueue”, “enqueue”, “dequeueCat”, “dequeueDog”, “dequeueAny”]
[[], [[0, 0]], [[1, 0]], [], [], []]
输出:
[null,null,null,[0,0],[-1,-1],[1,0]]

示例2:
输入:
[“AnimalShelf”, “enqueue”, “enqueue”, “enqueue”, “dequeueDog”, “dequeueCat”, “dequeueAny”]
[[], [[0, 0]], [[1, 0]], [[2, 1]], [], [], []]
输出:
[null,null,null,null,[2,1],[0,0],[1,0]]

单队列

class AnimalShelf {
    private Queue<int[]> queue;

    public AnimalShelf() {
        queue = new LinkedList<>();
    }
    
    public void enqueue(int[] animal) {
        queue.offer(animal);
    }
    
    public int[] dequeueAny() {
        return queue.isEmpty() ? new int[]{-1, -1} : queue.poll();
    }
    
    public int[] dequeueDog() {
        for (int[] animal : queue)
            if (animal[1] == 1) {
                queue.remove(animal);
                return animal;
            }  
        return new int[]{-1, -1};
    }
    
    public int[] dequeueCat() {
        for (int[] animal : queue)
            if (animal[1] == 0) {
                queue.remove(animal);
                return animal;
            }  
        return new int[]{-1, -1};
    }
}

双队列

class AnimalShelf {
    private Queue<Integer> cats;
    private Queue<Integer> dogs;

    public AnimalShelf() {
        cats = new LinkedList<>();
        dogs = new LinkedList<>();
    }
    
    public void enqueue(int[] animal) {
        if (animal[1] == 0)
            cats.offer(animal[0]);
        else
            dogs.offer(animal[0]);
    }
    
    public int[] dequeueAny() {
        if (cats.isEmpty() && dogs.isEmpty()) {
            return new int[]{-1, -1};
        } else if (cats.isEmpty() || (!dogs.isEmpty() && !cats.isEmpty() && dogs.peek() < cats.peek())) {
            return new int[]{dogs.poll(), 1};
        } else {
            return new int[]{cats.poll(), 0};
        }
    }
    
    public int[] dequeueDog() {
        return dogs.isEmpty() ? new int[]{-1, -1} : new int[]{dogs.poll(), 1};
    }
    
    public int[] dequeueCat() {
        return cats.isEmpty() ? new int[]{-1, -1} : new int[]{cats.poll(), 0};
    }
}

最小高度树

给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉搜索树。

示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
在这里插入图片描述

递归

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return inorder(nums, 0, nums.length - 1);
    }

    private TreeNode inorder(int[] nums, int start, int end) {
        if (start > end) 
            return null;
        
        int mid = start + (end - start) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = inorder(nums, start, mid - 1);
        root.right = inorder(nums, mid + 1, end);
        return root;
    }
}

栈排序

栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push、pop、peek 和 isEmpty。当栈为空时,peek 返回 -1。

示例1:
输入:
[“SortedStack”, “push”, “push”, “peek”, “pop”, “peek”]
[[], [1], [2], [], [], []]
输出:
[null,null,null,1,null,2]

示例2:
输入:
[“SortedStack”, “pop”, “pop”, “push”, “pop”, “isEmpty”]
[[], [], [], [1], [], []]
输出:
[null,null,null,null,null,true]

辅助栈

class SortedStack {
    private Deque<Integer> stack;
    private Deque<Integer> tmpStack;

    public SortedStack() {
        stack = new LinkedList<>();
        tmpStack = new LinkedList<>();
    }
    
    public void push(int val) {
        while (!stack.isEmpty() && stack.peek() < val)
            tmpStack.push(stack.pop());
        while (!tmpStack.isEmpty() && tmpStack.peek() > val)
            stack.push(tmpStack.pop());
        stack.push(val);
    }
    
    public void pop() {
        while (!tmpStack.isEmpty())
            stack.push(tmpStack.pop());
        if (!stack.isEmpty())
            stack.pop();
    }
    
    public int peek() {
        while (!tmpStack.isEmpty())
            stack.push(tmpStack.pop());
        return stack.isEmpty() ? -1 : stack.peek();
    }
    
    public boolean isEmpty() {
        return stack.isEmpty() && tmpStack.isEmpty();
    }
}

堆盘子

堆盘子。设想有一堆盘子,堆太高可能会倒下来。因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。SetOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。此外,SetOfStacks.push()和SetOfStacks.pop()应该与普通栈的操作方法相同(也就是说,pop()返回的值,应该跟只有一个栈时的情况一样)。 进阶:实现一个popAt(int index)方法,根据指定的子栈,执行pop操作。

当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop,popAt 应返回 -1.

示例1:
输入:
[“StackOfPlates”, “push”, “push”, “popAt”, “pop”, “pop”]
[[1], [1], [2], [1], [], []]
输出:
[null, null, null, 2, 1, -1]

示例2:
输入:
[“StackOfPlates”, “push”, “push”, “push”, “popAt”, “popAt”, “popAt”]
[[2], [1], [2], [3], [0], [0], [0]]
输出:
[null, null, null, null, 2, 1, 3]

栈+ArrayList

class StackOfPlates {
    private List<Deque> set;
    private int capacity;
    private int size;
    public StackOfPlates(int cap) {
        set = new ArrayList<>();
        capacity = cap;
        size = 0;
    }
    
    public void push(int val) {
        if (capacity == 0)
            return;
        if (size == 0 || set.get(size - 1).size() == capacity){
            set.add(new LinkedList<Integer>(){{push(val);}});
            size++;
        } else {
            set.get(size - 1).push(val);
        }
    }
    
    public int pop() {
        if (size == 0)
            return -1;
        int val = ((Deque<Integer>)set.get(size - 1)).pop();
        if (set.get(size - 1).size() == 0) 
            set.remove(--size);
        return val;
    }
    
    public int popAt(int index) {
        if (size <= index)
            return -1;
        Deque<Integer> stack = set.get(index);
        int val = stack.pop();
        if (stack.size() == 0) {
            set.remove(index);
            --size;
        }
        return val;
    }
}

节点间通路

节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。

示例1:
输入:n = 3, graph = [[0, 1], [0, 2], [1, 2], [1, 2]], start = 0, target = 2
输出:true

示例2:
输入:n = 5, graph = [[0, 1], [0, 2], [0, 4], [0, 4], [0, 1], [1, 3], [1, 4], [1, 3], [2, 3], [3, 4]], start = 0, target = 4
输出 true

List+Set保存边

class Solution {
    private Set[] edges;
    private boolean[] visited;
    public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {
        if (start == target)
            return true;
            
        edges = new Set[n];
        visited = new boolean[n];
        for (int i = 0; i < n; i++)
            edges[i] = new HashSet<Integer>();
        for (int[] edge : graph)
            if (edge[0] != edge[1])
                edges[edge[0]].add(edge[1]);
        
        return dfs(start, target);
    }

    private boolean dfs(int start, int target) {
        if (start == target) 
            return true;
        
        Set<Integer> set = edges[start];
        for (int i : set) {
            if (!visited[i]) {
                visited[i] = true;
                if (dfs(i, target))
                    return true;
                visited[i] = false;
            }
        }
        return false;
    }
}

递归

class Solution {
    public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {
        if (start == target) 
            return true;
        
        for (int[] edge : graph) {
            if (edge[1] == target) 
                return findWhetherExistsPath(n, graph, start, edge[0]);
        }
        return false;
    }
}

特定深度节点链表

给定一棵二叉树,设计一个算法,创建含有某一深度上所有节点的链表(比如,若一棵树的深度为 D,则会创建出 D 个链表)。返回一个包含所有深度的链表的数组。

示例:
输入:[1,2,3,4,5,null,7,8]
在这里插入图片描述
输出:[[1],[2,3],[4,5,7],[8]]

层次遍历

class Solution {
    public ListNode[] listOfDepth(TreeNode tree) {
        List<ListNode> result = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(tree);
        while (!queue.isEmpty()) {
            int size = queue.size();
            ListNode dummy = new ListNode(0), cur = dummy;
            while (size-- > 0) {
                TreeNode node = queue.poll();
                cur.next = new ListNode(node.val);
                cur = cur.next;
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
            result.add(dummy.next);
        }
        return (ListNode[])result.toArray(new ListNode[result.size()]);
    }
}

检查平衡性

实现一个函数,检查二叉树是否平衡。在这个问题中,平衡树的定义如下:任意一个节点,其两棵子树的高度差不超过 1。

示例 1:
给定二叉树 [3,9,20,null,null,15,7]
在这里插入图片描述
返回 true 。

示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
在这里插入图片描述
返回 false 。

自底向上递归

class Solution {
    public boolean isBalanced(TreeNode root) {
        if (root == null)
            return true;
        return height(root) != -1;
    }

    private int height(TreeNode root) {
        if (root == null)
            return 0;
        
        int lHeight = height(root.left);
        if (lHeight == -1)
            return -1;
        int rHeight = height(root.right);
        if (rHeight == -1 || Math.abs(lHeight - rHeight) > 1)
            return -1;
        return Math.max(lHeight, rHeight) + 1;
    }
}

合法二叉搜索树

实现一个函数,检查一棵二叉树是否为二叉搜索树。

示例 1:
输入:
在这里插入图片描述
输出: true

示例 2:
输入:
在这里插入图片描述
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。

自下而上递归

class Solution {
    private TreeNode pre = null;
    public boolean isValidBST(TreeNode root) {
        if (root == null)
            return true;
        boolean left = isValidBST(root.left);
        if (pre != null && pre.val >= root.val)
            return false;
        pre = root;
        boolean right = isValidBST(root.right);
        return left && right;
    }
}

自上而下递归

class Solution {
    public boolean isValidBST(TreeNode root) {
        return recursion(root, null, null);
    }

    public boolean recursion(TreeNode root, Integer lower, Integer upper) {
        if (root == null) 
            return true;

        int val = root.val;
        if ((lower != null && val <= lower) || (upper != null && val >= upper)) 
            return false;
        if (!recursion(root.right, val, upper) || !recursion(root.left, lower, val)) 
            return false;
        return true;
    }
}

中序遍历

class Solution {
    public boolean isValidBST(TreeNode root) {
        Deque<TreeNode> stack = new LinkedList<>();
        Integer inorder = null;
        while (!stack.isEmpty() || root != null) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            if (inorder != null && root.val <= inorder) 
                return false;
            inorder = root.val;
            root = root.right;
        }
        return true;
    }
}

后继者

设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。
如果指定节点没有对应的“下一个”节点,则返回null。

示例 1:
在这里插入图片描述
输入: root = [2,1,3], p = 1
输出: 2

示例 2:
在这里插入图片描述
输入: root = [5,3,6,2,4,null,null,1], p = 6
输出: null

递归

找到该节点右子树的最左节点。

class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if (root == null)
            return null;

        if (p.val >= root.val)
            return inorderSuccessor(root.right, p);
        else {
            TreeNode left = inorderSuccessor(root.left, p);
            return left != null ? left : root;
        }
    }
}

迭代

class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if (p.right != null) {
            p = p.right;
            while (p.left != null)
                p = p.left;
            return p;
        }
        
        TreeNode result = null;
        while (p != root) {
            if (p.val > root.val) {
                root = root.right;
            } else {
                result = root;
                root = root.left;
            }
        }
        return result;
    }
}

首个共同祖先

设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
在这里插入图片描述

示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

递归

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null)
            return null;
        if (root == p || root == q)
            return root;

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left != null && right != null)
            return root;
        return left != null ? left : right;
    }
}

检查子树

检查子树。你有两棵非常大的二叉树:T1,有几万个节点;T2,有几万个节点。设计一个算法,判断 T2 是否为 T1 的子树。
如果 T1 有这么一个节点 n,其子树与 T2 一模一样,则 T2 为 T1 的子树,也就是说,从节点 n 处把树砍断,得到的树与 T2 完全相同。
注意:此题相对书上原题略有改动。

示例1:
输入:t1 = [1, 2, 3], t2 = [2]
输出:true

示例2:
输入:t1 = [1, null, 2, 4], t2 = [3, 2]
输出:false、

递归

class Solution {
    public boolean checkSubTree(TreeNode t1, TreeNode t2) {
        if (t2 == null)
            return true;
        else if (t1 == null)
            return false;
        else if (t1.val == t2.val && isSameTree(t1, t2))
            return true;
        return checkSubTree(t1.left, t2) || checkSubTree(t1.right, t2);
    }

    private boolean isSameTree(TreeNode t1, TreeNode t2) {
        if (t1 == null && t2 == null)
            return true;
        else if (t1 == null || t2 == null)
            return false;
        else if (t1.val != t2.val)
            return false;
        
        return isSameTree(t1.left, t2.left) && isSameTree(t1.right, t2.right);
    }
}

求和路径

给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束,但是其方向必须向下(只能从父节点指向子节点方向)。

示例:
给定如下二叉树,以及目标和 sum = 22,
在这里插入图片描述
返回:
3
解释:和为 22 的路径有:[5,4,11,2], [5,8,4,5], [4,11,7]

前缀和

class Solution {
    private int count = 0;
    public int pathSum(TreeNode root, int sum) {
        dfs(root, new ArrayList<>(){{add(0);}}, sum);
        return count;
    }

    private void dfs(TreeNode root, List<Integer> preSum, int target) {
        if (root == null)
            return;
        
        int sum = preSum.get(preSum.size() - 1) + root.val;
        for (int pre : preSum)
            if (sum - pre == target)
                count++;
        
        preSum.add(sum);
        dfs(root.left, preSum, target);
        dfs(root.right, preSum, target);
        preSum.remove(preSum.size() - 1);
    }
}

保存路径,每层求和

class Solution {
    private int count = 0;
    public int pathSum(TreeNode root, int sum) {
        int depth = maxDepth(root);
        int[] path = new int[depth];
        dfs(root, path, 0, sum);
        return count;
    }

    private void dfs(TreeNode root, int[] path, int level, int target) {
        if (root==null)
            return;
           
        path[level] = root.val;
        int sum = 0;
        for (int i = level; i >=0; i--) {
            sum += path[i];
            if(target == sum)
                count++;
        }
        dfs(root.left, path, level + 1, target);
        dfs(root.right, path, level + 1, target);
    }

     private int maxDepth(TreeNode root) {
        if (root == null)
            return 0;
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

二叉搜索树序列

从左向右遍历一个数组,通过不断将其中的元素插入树中可以逐步地生成一棵二叉搜索树。给定一个由不同节点组成的二叉搜索树,输出所有可能生成此树的数组。

示例:
给定如下二叉树
在这里插入图片描述
返回:
[
[2,1,3],
[2,3,1]
]

遍历

要得到所有可能生成此树的数组,即求这棵二叉树的所有遍历方式。只有遍历过一个节点,其子节点才可达。同一层的节点有多种排列,所以使用过一个节点后,应将其放回队列。

class Solution {
    private List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> BSTSequences(TreeNode root) {
        if (root == null) {
            result.add(new ArrayList<Integer>());
            return result;
        }
            
        
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offerLast(root);
        backtrack(queue, new ArrayList<Integer>());
        return result;
    }

    private void backtrack(Deque<TreeNode> queue, List<Integer> combine) {
        if (queue.isEmpty()) {
            result.add(new ArrayList<>(combine));
            return;
        }

        int size = queue.size();
        while(size-- > 0) {
            TreeNode node = queue.pollFirst();
            combine.add(node.val);

            int childNum = 0;
            if (node.left != null) {
                queue.offerLast(node.left);
                childNum++;
            }
            if (node.right != null) {
                queue.offerLast(node.right);
                childNum++;
            }

            backtrack(queue, combine);

            while (childNum-- > 0)
                queue.pollLast();
            queue.offerLast(node);
            combine.remove(combine.size() - 1);
        }
    }
}

插入

给定两个整型数字 N 与 M,以及表示比特位置的 i 与 j(i <= j,且从 0 位开始计算)。
编写一种方法,使 M 对应的二进制数字插入 N 对应的二进制数字的第 i ~ j 位区域,不足之处用 0 补齐。具体插入过程如图所示。

题目保证从 i 位到 j 位足以容纳 M, 例如: M = 10011,则 i~j 区域至少可容纳 5 位。
在这里插入图片描述

示例1:
输入:N = 1024(10000000000), M = 19(10011), i = 2, j = 6
输出:N = 1100(10001001100)

示例2:
输入: N = 0, M = 31(11111), i = 0, j = 4
输出:N = 31(11111)

位运算

class Solution {
    public int insertBits(int N, int M, int i, int j) {
        for (int k = i; k <= j; k++) 
            N &= ~(1 << k);
        return N | (M << i);
    }
}

二进制数转字符串

二进制数转字符串。给定一个介于0和1之间的实数(如0.72),类型为double,打印它的二进制表达式。如果该数字无法精确地用32位以内的二进制表示,则打印“ERROR”。

示例1:
输入:0.625
输出:“0.101”

示例2:
输入:0.1
输出:“ERROR”
提示:0.1无法被二进制准确表示

迭代

class Solution {
    public String printBin(double num) {
        StringBuilder sb = new StringBuilder("0.");
        while (num != 0.0) {
            num *= 2;
            if (num >= 1.0) {
                sb.append(1);
                num -= 1;
            } else {
                sb.append(0);
            }
            if (sb.length() > 32)
                return "ERROR";
        }
        return sb.toString();
    }
}

翻转数位

给定一个32位整数 num,你可以将一个数位从0变为1。请编写一个程序,找出你能够获得的最长的一串1的长度。

示例 1:
输入: num = 1775(110111011112)
输出: 8

示例 2:
输入: num = 7(01112)
输出: 4

位运算

class Solution {
    public int reverseBits(int num) {
        int last = 0, count = 0, max = 0;
        for (int i = 0; i < 32; i++) {
            if ((num & 1) == 1) {
                count++;
            } else {
                last = count + 1;
                count = 0;
            }
            max = Math.max(max, count + last);
            num >>= 1;
        }
        return max;
    }
}

下一个数

下一个数。给定一个正整数,找出与其二进制表达式中1的个数相同且大小最接近的那两个数(一个略大,一个略小)。

示例1:
输入:num = 2(或者0b10)
输出:[4, 1] 或者([0b100, 0b1])

示例2:
输入:num = 1
输出:[2, -1]

位运算

  • 比当前小的最接近的数:从低向高找第一个“10”,将它转换为“01”,低位的1全部向高位靠;
  • 比当前大的最接近的数:从低向高找第一个“01”,将它转换为“10”,低位的1全部向低位靠。
class Solution {
    public int[] findClosedNumbers(int num) {
        int[] result = new int[]{-1, -1};
        int temp = num, count = 0, end = -1;
        for (int i = 0; i < 30; i++) {
            if ((num & (1 << i)) == 0 && (num & (1 << (i + 1))) != 0) {
                end = i - 1;
                temp |= (1 << i);
                temp &= ~(1 << (i + 1));
                if (count > 0) {
                    temp &= ((-1 >> end) << end);
                    temp |= (((int)Math.pow(2, count) - 1) << (end - count + 1));
                }
                result[1] = temp;
                break;
            }
            if ((num & (1 << i)) != 0)
                count++;
        }

        temp = num; count = 0; end = -1;
        for (int i = 0; i < 30; i++) {
            if ((num & (1 << i)) != 0 && (num & (1 << (i + 1))) == 0) {
                end = i;
                temp &= ~(1 << i);
                temp |= (1 << (i + 1));
                if (count > 0) {
                    temp &= ((-1 >> end) << end);
                    temp |= ((int)Math.pow(2, count) - 1);
                }
                result[0] = temp;
                break;
            }
            if ((num & (1 << i)) != 0)
                count++;
        }
        
        return result;
    }
}

整数转换

整数转换。编写一个函数,确定需要改变几个位才能将整数A转成整数B。

示例1:
输入:A = 29 (或者0b11101), B = 15(或者0b01111)
输出:2

示例2:
输入:A = 1,B = 2
输出:2

异或

class Solution {
    public int convertInteger(int A, int B) {
        int xor = A ^ B;
        int count = 0;
        while (xor != 0) {
            if ((xor & 1) != 0)
                count++;
            xor >>>= 1;
        }
        return count;
    }
}

配对交换

配对交换。编写程序,交换某个整数的奇数位和偶数位,尽量使用较少的指令(也就是说,位0与位1交换,位2与位3交换,以此类推)。

示例1:
输入:num = 2(或者0b10)
输出 1 (或者 0b01)

示例2:
输入:num = 3
输出:3

位运算

class Solution {
    public int exchangeBits(int num) {
        int odd  = 0x55555555;
        int even = 0xaaaaaaaa;
        return ((num & odd) << 1) | ((num & even) >> 1);
    }
}

绘制直线

绘制直线。有个单色屏幕存储在一个一维数组中,使得32个连续像素可以存放在一个 int 里。屏幕宽度为w,且w可被32整除(即一个 int 不会分布在两行上),屏幕高度可由数组长度及屏幕宽度推算得出。请实现一个函数,绘制从点(x1, y)到点(x2, y)的水平线。

给出数组的长度 length,宽度 w(以比特为单位)、直线开始位置 x1(比特为单位)、直线结束位置 x2(比特为单位)、直线所在行数 y。返回绘制过后的数组。

示例1:
输入:length = 1, w = 32, x1 = 30, x2 = 31, y = 0
输出:[3]
说明:在第0行的第30位到第31为画一条直线,屏幕表示为[0b000000000000000000000000000000011]

示例2:
输入:length = 3, w = 96, x1 = 0, x2 = 95, y = 0
输出:[-1, -1, -1]

位运算

class Solution {
    public int[] drawLine(int length, int w, int x1, int x2, int y) {
        int[] result = new int[length];
        int width = w / 32;
        int start = width * y, end = width * (y + 1);
        int num = x2 - x1 + 1;
        for (int i = start; i < end && num != 0; i++) {
            while (x1 < 32 && num-- > 0) 
                result[i] |= 1 << (31 - x1++);
            x1 -= 32;
        }
        return result;
    }
}

三步问题

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

示例1:
输入:n = 3
输出:4
说明: 有四种走法

示例2:
输入:n = 5
输出:13

动态规划

class Solution {
    public int waysToStep(int n) {
        if (n < 4)
            return n == 3 ? 4 : n;
            
        int dp1 = 1, dp2 = 2, dp3 = 4;
        for (int i = 4; i <= n; i++) {
            int tmp1 = dp2, tmp2 = dp3;
            dp3 = ((dp1 + dp2) % 1000000007 + dp3) % 1000000007;
            dp1 = tmp1;
            dp2 = tmp2;
        }
        return dp3;
    }
}

迷路的机器人

设想有个机器人坐在一个网格的左上角,网格 r 行 c 列。机器人只能向下或向右移动,但不能走到一些被禁止的网格(有障碍物)。设计一种算法,寻找机器人从左上角移动到右下角的路径。
在这里插入图片描述

网格中的障碍物和空位置分别用 1 和 0 来表示。

返回一条可行的路径,路径由经过的网格的行号和列号组成。左上角为 0 行 0 列。如果没有可行的路径,返回空数组。

示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: [[0,0],[0,1],[0,2],[1,2],[2,2]]
解释:
输入中标粗的位置即为输出表示的路径,即
0行0列(左上角) -> 0行1列 -> 0行2列 -> 1行2列 -> 2行2列(右下角)

回溯

class Solution {
    private int rows, cols;
    public List<List<Integer>> pathWithObstacles(int[][] obstacleGrid) {
        rows = obstacleGrid.length;
        cols = obstacleGrid[0].length;
        List<List<Integer>> path = new ArrayList<>();
        if (obstacleGrid[rows - 1][cols - 1] == 0)
            return path;
        return backtrack(obstacleGrid, 0, 0, path) ? path : new ArrayList<>();
    }

    private boolean backtrack(int[][] obstacleGrid, int i, int j, List<List<Integer>> path) {
        if (i >= rows || j >= cols || obstacleGrid[i][j] == 1)
            return false;
        
        path.add(new ArrayList<Integer>(){{add(i); add(j);}});
        if ((i == rows - 1 && j == cols - 1) || backtrack(obstacleGrid, i, j + 1, path) || backtrack(obstacleGrid, i + 1, j, path)) 
            return true;

        path.remove(path.size() - 1);
        obstacleGrid[i][j] = 1;
        return false;
    }
}

魔术索引

魔术索引。 在数组A[0…n-1]中,有所谓的魔术索引,满足条件A[i] = i。给定一个有序整数数组,编写一种方法找出魔术索引,若有的话,在数组A中找出一个魔术索引,如果没有,则返回-1。若有多个魔术索引,返回索引值最小的一个。

示例1:
输入:nums = [0, 2, 3, 4, 5]
输出:0
说明: 0下标的元素为0

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

二分查找

class Solution {
    public int findMagicIndex(int[] nums) {
        return binarySearch(nums, 0, nums.length - 1);
    }

    private int binarySearch(int[] nums, int left, int right) {
        if (left > right)
            return -1;

        int mid = left + (right - left) / 2;
        int leftIndex = binarySearch(nums, left, mid - 1);
        if (leftIndex != -1)
            return leftIndex;
        else if (mid == nums[mid])
            return mid;
        else
            return binarySearch(nums, mid + 1, right);
    }
}

幂集

幂集。编写一种方法,返回某集合的所有子集。集合中不包含重复的元素。
说明:解集不能包含重复的子集。

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

回溯

class Solution {
    private List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(nums, 0, new ArrayList<Integer>());
        return result;
    }

    private void dfs(int[] nums, int start, List<Integer> combine) {
        result.add(new ArrayList<>(combine));
        for (int i = start; i < nums.length; i++) {
            combine.add(nums[i]);
            dfs(nums, i + 1, combine);
            combine.remove(combine.size() - 1);
        }
    }
}

迭代

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        result.add(new ArrayList<Integer>());
        for(int num : nums) {
            int n = result.size();
            for(int j = 0; j < n; j++) {
                List<Integer> list = new ArrayList<>(result.get(j));
                list.add(num);
                result.add(list);
            }
        }
        return result;
    }
}

递归乘法

递归乘法。 写一个递归函数,不使用 * 运算符, 实现两个正整数的相乘。可以使用加号、减号、位移,但要吝啬一些。

示例1:
输入:A = 1, B = 10
输出:10

示例2:
输入:A = 3, B = 4
输出:12

位运算

class Solution {
    public int multiply(int A, int B) {
        return ((B & 1) == 1 ? A : 0) + (B > 1 ? multiply(A + A, B >> 1) : 0);
    }
}

汉诺塔问题

在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。

你需要原地修改栈。

示例1:
输入:A = [2, 1, 0], B = [], C = []
输出:C = [2, 1, 0]

示例2:
输入:A = [1, 0], B = [], C = []
输出:C = [1, 0]

分治

在这里插入图片描述

class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        move(A.size(), A, B, C);
    }

    private void move(int n, List<Integer> origin, List<Integer> auxiliary , List<Integer> target) {
        if (n == 1) {
            target.add(origin.remove(origin.size() - 1));
            return;
        }
        move(n - 1, origin, target, auxiliary);
        target.add(origin.remove(origin.size() - 1));
        move(n - 1, auxiliary, origin, target);
    }
}

无重复字符串的排列组合

无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。

示例1:
输入:S = “qwe”
输出:[“qwe”, “qew”, “wqe”, “weq”, “ewq”, “eqw”]

示例2:
输入:S = “ab”
输出:[“ab”, “ba”]

回溯

class Solution {
    public String[] permutation(String S) {
        char[] charArray = S.toCharArray();
        List<String> result = new ArrayList<>();
        backtrack(charArray, result, 0);
        return result.toArray(new String[result.size()]);
    }

    private void backtrack(char[] charArray, List<String> result, int k) {
        int len = charArray.length;
        if (k == charArray.length - 1) {
            result.add(String.valueOf(charArray));
            return;
        }
        for (int i = k; i < len; i++) {
            swap(charArray, k, i);
            backtrack(charArray, result, k + 1);
            swap(charArray, k, i);
        }
    }

    private void swap(char[] charArray, int i, int j) {
        char tmp = charArray[i];
        charArray[i] = charArray[j];
        charArray[j] = tmp;
    }
}

有重复字符串的排列组合

有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。

示例1:
输入:S = “qqe”
输出:[“eqq”,“qeq”,“qqe”]

示例2:
输入:S = “ab”
输出:[“ab”, “ba”]

回溯

class Solution {
    private int len;
    private boolean[] used;
    private List<String> result = new ArrayList<>();
    public String[] permutation(String S) {
        len = S.length();
        char[] charArray = S.toCharArray();
        Arrays.sort(charArray);
        used = new boolean[len];
        backtrack(charArray, new StringBuilder());
        return result.toArray(new String[result.size()]);
    }

    private void backtrack(char[] charArray, StringBuilder combine) {
        if (combine.length() == len) {
            result.add(combine.toString());
            return;
        }
        for (int i = 0; i < len; i++) {
            if (used[i] || (i > 0 && charArray[i - 1] == charArray[i] && !used[i - 1]))
                continue;
            used[i] = true;
            combine.append(charArray[i]);
            backtrack(charArray, combine);
            combine.deleteCharAt(combine.length() - 1);
            used[i] = false;
        }
    }
}

括号

括号。设计一种算法,打印n对括号的所有合法的(例如,开闭一一对应)组合。
说明:解集不能包含重复的子集。

例如,给出 n = 3,生成结果为:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]

回溯

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> results = new ArrayList<>();
        if (n == 0)
            return results;
            
        backtrack(results, new StringBuilder(), 0, 0, n);
        return results;
    }

    private void backtrack(List<String> results, StringBuilder s, int open, int close, int n) {
        if (s.length() == n * 2) {
            results.add(s.toString());
            return;
        }

        if (open < n) {
            s.append('(');
            backtrack(results, s, open + 1, close, n);
            s.deleteCharAt(s.length() - 1);
        }
        if (close < open) {
            s.append(')');
            backtrack(results, s, open, close + 1, n);
            s.deleteCharAt(s.length() - 1);
        }
    }
}

颜色填充

编写函数,实现许多图片编辑软件都支持的「颜色填充」功能。
待填充的图像用二维数组 image 表示,元素为初始颜色值。初始坐标点的行坐标为 sr 列坐标为 sc。需要填充的新颜色为 newColor 。
「周围区域」是指颜色相同且在上、下、左、右四个方向上存在相连情况的若干元素。
请用新颜色填充初始坐标点的周围区域,并返回填充后的图像。

示例:
输入:
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
输出:[[2,2,2],[2,2,0],[2,0,1]]
解释:
初始坐标点位于图像的正中间,坐标 (sr,sc)=(1,1) 。
初始坐标点周围区域上所有符合条件的像素点的颜色都被更改成 2 。
注意,右下角的像素没有更改为 2 ,因为它不属于初始坐标点的周围区域。

DFS

class Solution {
    private int rows, cols;
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        rows = image.length;
        cols = image[0].length;
        if (image[sr][sc] != newColor)
            dfs(image, sr, sc, image[sr][sc], newColor);
        return image;
    }

    private void dfs(int[][] image, int i, int j, int oldColor, int newColor) {
        if (i < 0 || i >= rows || j < 0 || j >= cols || image[i][j] != oldColor)
            return;
        
        image[i][j] = newColor;
        dfs(image, i - 1, j, oldColor, newColor);
        dfs(image, i + 1, j, oldColor, newColor);
        dfs(image, i, j - 1, oldColor, newColor);
        dfs(image, i, j + 1, oldColor, newColor);
    }
}

硬币

硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)

示例1:
输入: n = 5
输出:2
解释: 有两种方式可以凑成总金额:
5=5
5=1+1+1+1+1

示例2:
输入: n = 10
输出:4
解释: 有四种方式可以凑成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1

动态规划

i i i为硬币个数, v v v为硬币总价值。
f ( i , v ) = f ( i − 1 , v ) + f ( i − 1 , v − c i ) + f ( i − 1 , v − 2 c i ) ⋯ f ( i − 1 , v − k c i ) f(i, v) = f(i - 1, v) + \color{red}{ f(i - 1, v - c_i) + f(i - 1, v - 2 c_i) \cdots f(i - 1, v - k c_i) } f(i,v)=f(i1,v)+f(i1,vci)+f(i1,v2ci)f(i1,vkci) k + 1 k + 1 k+1 项,其中 k = ⌊ v c i ⌋ k = \lfloor \frac{v}{c_i} \rfloor k=civ
那么我们可以得到使用 v − c i v - c_i vci替换 v v v,得到: f ( i , v − c i ) = f ( i − 1 , v − c i ) + f ( i − 1 , v − 2 c i ) + f ( i − 1 , v − 3 c i ) ⋯ f ( i − 1 , v − k c i ) f(i, v - c_i) = \color{red}{ f(i - 1, v - c_i) + f(i - 1, v - 2 c_i) + f(i - 1, v - 3 c_i) \cdots f(i - 1, v - k c_i) } f(i,vci)=f(i1,vci)+f(i1,v2ci)+f(i1,v3ci)f(i1,vkci) k k k 项。
注意到上面两个方程中标成红色的 k k k 项是完全相同的,于是我们可以用下面式子的左半部分 f ( i , v − c i ) f(i, v - c_i) f(i,vci)等价替换上面式子红色的 k k k 项,得到化简后的转移方程: f ( i , v ) = f ( i − 1 , v ) + f ( i , v − c i ) f(i, v) = f(i - 1, v) + f(i, v - c_i) f(i,v)=f(i1,v)+f(i,vci)

class Solution {
    public int waysToChange(int n) {
        int[] coins = {25, 10, 5, 1};
        int[] dp = new int[n + 1];
        dp[0] = 1;
        for (int coin :coins) {
            for (int i = coin; i <= n; i++)
                dp[i] = (dp[i] + dp[i - coin]) % 1000000007;
        }
        return dp[n];
    }
}

数学法

分别遍历25、10、5三种硬币的可能数目,即可得到组合数。

class Solution {
    public int waysToChange(int n) {
        int result = 0;
        for (int i25 = 0; i25 <= n / 25; i25++) {
            int rest1 = n - i25 * 25;
            for (int i10 = 0; i10 <= rest1 / 10; i10++) {
                int rest2 = rest1 - i10 * 10;
                for (int i5 = 0; i5 <= rest2 / 5; i5++)
                    result++;
            }
        }
        return result;
    }
}

将内循环进行化简。

class Solution {
    public int waysToChange(int n) {
        int result = 0;
        for (int i25 = 0; i25 <= n / 25; i25++) {
            int rest1 = n - i25 * 25;
            for (int i10 = 0; i10 <= rest1 / 10; i10++) 
                result += (rest1 - i10 * 10) / 5 + 1;
        }
        return result;
    }
}

可以看出当前的内循环是一个等差数列求和,因此进一步化简后得到

class Solution {
    public int waysToChange(int n) {
        int result = 0;
        for (int i= 0; i <= n / 25; i++) {
            int rest = n - i* 25;
            // result += ((rest1 / 5 + 1) + (rest1 % 10 / 5 + 1)) * (rest1 / 10 + 1) / 2;
            result += (rest / 10 + 1) * (rest / 10 + rest % 10 / 5 + 1);
        }
        return result;
    }
}

八皇后

设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。
注意:本题相对原题做了扩展

示例:
输入:4
输出:[[".Q…","…Q",“Q…”,"…Q."],["…Q.",“Q…”,"…Q",".Q…"]]
解释: 4 皇后问题存在如下两个不同的解法。
[
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]

回溯法

class Solution {
    private boolean[] top, leftTop, rightTop;
    private List<List<String>> result = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        top = new boolean[n];
        leftTop = new boolean[2 * n];
        rightTop = new boolean[2 * n];

        char[] chars = new char[n];
        Arrays.fill(chars, '.');
        backtrack(chars, new ArrayList<>(), 0, n);

        return result;
    }

    private void backtrack(char[] chars, List<String> combine, int row, int n) {
        if (row == n) {
            result.add(new ArrayList<>(combine));
            return;
        }

        for (int i = 0; i < n; i++) {
            if (top[i] || leftTop[row - i + n] || rightTop[row + i])
                continue;
                
            chars[i] = 'Q';
            combine.add(new String(chars));
            chars[i] = '.';
            setState(i, row, n, true);

            backtrack(chars, combine, row + 1, n);

            setState(i, row, n, false);
            combine.remove(combine.size() - 1);
        }
    }

    private void setState(int i, int row, int n, boolean state) {
        top[i] = state;
        leftTop[row - i + n] = state;
        rightTop[row + i] = state;
    }
}

基于位运算的回溯法

class Solution {
    private List<List<String>> result = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {

        char[] chars = new char[n];
        Arrays.fill(chars, '.');
        backtrack(chars, new ArrayList<>(), n, 0, 0, 0);

        return result;
    }

    private void backtrack(char[] chars, List<String> combine, int n, int top, int leftTop, int rightTop) {
        if (combine.size() == n) {
            result.add(new ArrayList<>(combine));
            return;
        }

        int available = ((1 << n) - 1) & (~(top | leftTop | rightTop));
        while (available != 0) {
            int pos = available & -available;
            available = available & (available - 1);
            int col = Integer.bitCount(pos - 1);
            chars[col] = 'Q';
            combine.add(new String(chars));
            chars[col] = '.';
            
            backtrack(chars, combine, n, top | pos, (leftTop | pos) << 1, (rightTop | pos) >> 1);
            combine.remove(combine.size() - 1);
        }
    }
}

布尔运算

给定一个布尔表达式和一个期望的布尔结果 result,布尔表达式由 0 (false)、1 (true)、& (AND)、 | (OR) 和 ^ (XOR) 符号组成。实现一个函数,算出有几种可使该表达式得出 result 值的括号方法。

示例 1:
输入: s = “1^0|0|1”, result = 0
输出: 2
解释: 两种可能的括号方法是
1^(0|(0|1))
1^((0|0)|1)

示例 2:
输入: s = “0&0&0&1^1|0”, result = 1
输出: 10

动态规划

class Solution {
    private char[] chars;
    private int[][][] dp;
    public int countEval(String s, int result) {
        chars = s.toCharArray();
        int len = chars.length;

        dp = new int[len][len][2];
        for (int i = 0; i < len; i+=2)
            for (int j = 0; j < len; j+=2)
                Arrays.fill(dp[i][j], -1);
        
        return getCount(0, len - 1, result);
    }

    private int getCount(int start, int end, int result) {
        if (start == end) 
            return ((chars[start] - '0') == result) ? 1 : 0;
        
        if (dp[start][end][result] != -1)
            return dp[start][end][result];

        dp[start][end][result] = 0;
        for (int k = start + 1; k < end; k += 2) {
            char operator = chars[k];
            for (int i = 0; i <= 1; i++)
                for (int j = 0; j <= 1; j++)
                    if (calculate(operator, i, j) == result) 
                        dp[start][end][result] += getCount(start, k - 1, i) * getCount(k + 1, end, j);
        }
        return dp[start][end][result];
    }

    private int calculate(char operator, int val1, int val2) {
        switch (operator) {
            case '^':
                return val1 ^ val2;
            case '&':
                return val1 & val2;
            case '|':
                return val1 | val2;
        }
        return -1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值