【java刷题】力扣 程序员面试金典(第 6 版)

【java刷题】力扣 程序员面试金典(第 6 版)

面试题 01.01. 判定字符是否唯一

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

示例 1:

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

示例 2:

输入: s = "abc"
输出: true

限制:

  • 0 <= len(s) <= 100
  • 如果你不使用额外的数据结构,会很加分。

解答1:

//直接使用HashSet记录
class Solution {
    public boolean isUnique(String astr) {
        HashSet<Character> m = new HashSet<>();
        for(char c : astr.toCharArray()){
            if(m.contains(c)){
                return false;
            }else{
                m.add(c);
            }
        }
        return true;
    }
}

解答2:

//如果你不使用额外的数据结构,会很加分。
//使用两个long共128位存储,0为不存在,1为已存在。
class Solution {
    public boolean isUnique(String astr) {
        long L = 0;
        long R = 0;
        for(char c : astr.toCharArray()){
            if(c<64){
                long t = 1<<c;
                if((t&L)!=0) return false;
                L |= t;
            }else{
                long t = 1<<(c-64);
                if((t&R)!=0) return false;
                R |= t;
            }
        }
        return true;
    }
}

面试题 01.02. 判定是否互为字符重排

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

示例 1:

输入: s1 = "abc", s2 = "bca"
输出: true 

示例 2:

输入: s1 = "abc", s2 = "bad"
输出: false

说明:

  • 0 <= len(s1) <= 100
  • 0 <= len(s2) <= 100

解答1:

//直接使用HashMap记录
class Solution {
    public boolean CheckPermutation(String s1, String s2) {
        if(s1.length()!=s2.length()) return false;
        Map<Character,Integer> m=new HashMap<>();
        int n = s1.length();
        for(char c : s1.toCharArray()){
            m.put(c, m.getOrDefault(c,0)+1);
        }
        for(char c : s2.toCharArray()){
            int t = m.getOrDefault(c,0);
            if(t==0) return false;
            else{
                m.put(c, t-1);
            }
        }
        return true;
    }
}

解答2:

//char数组排序
class Solution {
    public boolean CheckPermutation(String s1, String s2) {
        char[] c1 = s1.toCharArray();
        char[] c2 = s2.toCharArray();
        Arrays.sort(c1);
        Arrays.sort(c2);
        return new String(c1).equals(new String(c2));
    }
}

解答3:

//样例中字符串只有26个小写字母组成,使用数组记录。
class Solution {
    public boolean CheckPermutation(String s1, String s2) {
        if(s1.length()!=s2.length()) return false;
        int[] m = new int[26];
        for(char c : s1.toCharArray()){
            m[c-'a']++;
        }
        for(char c : s2.toCharArray()){
            if(--m[c-'a']<0){
                return false;
            }
        }
        return true;
    }
}

面试题 01.03. URL化

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

示例 1:

输入:"Mr John Smith    ", 13
输出:"Mr%20John%20Smith"

示例 2:

输入:"               ", 5
输出:"%20%20%20%20%20"

提示:

  • 字符串长度在 [0, 500000] 范围内。

解答1:

//StringBuilder逐个拼接
class Solution {
    public String replaceSpaces(String S, int length) {
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<length;i++){
            char c = S.charAt(i);
            if(c==' ') sb.append("%20");
            else{
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

解答2:

//java函数replaceAll
class Solution {
    public String replaceSpaces(String S, int length) {
       return S.substring(0, length).replaceAll(" ", "%20");
    }
}

解答3:

//读题发现,S的长度刚好是转化后的长度,直接在原char数组上进行转换
class Solution {
    public String replaceSpaces(String S, int length) {
       char[] c = S.toCharArray();
       int i=length-1, j=S.length()-1;
       while(i>=0){
           if(c[i]!=' '){
               c[j] = c[i];
               j--;
           }else{
               c[j--] = '0';
               c[j--] = '2';
               c[j--] = '%';
           }
           i--;
       }
       return new String(c, j+1, c.length - j - 1);
    }
}

面试题 01.04. 回文排列

给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。

回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。

回文串不一定是字典当中的单词。

示例1:

输入:"tactcoa"
输出:true(排列有"tacocat"、"atcocta",等等)

解答1:

class Solution {
    public boolean canPermutePalindrome(String s) {
        Map<Character,Integer> m = new HashMap<>();
        for(char c : s.toCharArray()){
            m.put(c,m.getOrDefault(c,0)+1);
        }
        int num = 0;
        for(int t : m.values()){
            if(t%2==1){
                num++;
                if(num>1) return false;
            }
        }
        return true;
    }
}

解答2:

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

面试题 01.05. 一次编辑

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

示例 1:

输入: 
first = "pale"
second = "ple"
输出: True

示例 2:

输入: 
first = "pales"
second = "pal"
输出: False

解答:

class Solution {
    public boolean oneEditAway(String first, String second) {
        int n = first.length(), m = second.length();
        if(n>m){
            return oneEditAway(second, first);
        }
        if(m-n>1){
            return false;
        }
        if(n==m){
            int num = 0;
            for(int i=0;i<n;i++){
                if(first.charAt(i)!=second.charAt(i)){
                    if(++num>1){
                        return false;
                    }
                }
            }
            return true;
        }
        for(int i = 0,t = 0;i<n;){
            if(first.charAt(i)!=second.charAt(i+t)){
                if(++t>1){
                    return false;
                }
            }else{
                i++;
            }
        }
        return true;
    }
}

面试题 01.06. 字符串压缩

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

示例1:

 输入:"aabcccccaaa"
 输出:"a2b1c5a3"

示例2:

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

提示:

  1. 字符串长度在[0, 50000]范围内。

解答:

class Solution {
    public String compressString(String S) {
        int n = S.length();
        if(n<=2) return S;
        char t = S.charAt(0);
        int num = 1;
        StringBuilder sb = new StringBuilder();
        for(int i=1;i<n;i++){
            char c = S.charAt(i);
            if(c==t){
                num++;
            }else{
                sb.append(t).append(num);
                num=1;
                t = c;
            }
        }
        sb.append(t).append(num);
        return n<=sb.length() ? S : sb.toString();
    }
}

面试题 01.07. 旋转矩阵

给你一幅由 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=0; j<n; j++) {
                matrix[i][j] ^= matrix[n-i-1][j];
                matrix[n-i-1][j] ^= matrix[i][j];
                matrix[i][j] ^= matrix[n-i-1][j];
            }
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<i;j++){
                matrix[i][j] ^= matrix[j][i];
                matrix[j][i] ^= matrix[i][j];
                matrix[i][j] ^= matrix[j][i];
            }
        }
    }
}

面试题 01.08. 零矩阵

编写一种算法,若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]
]

解答1:

//遍历两遍,第一遍记录需要0的位置,第二次进行修改
class Solution {
    public void setZeroes(int[][] matrix) {
        int n = matrix.length, m = matrix[0].length;       
        int[] a = new int[n];
        int[] b = new int[m];
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(matrix[i][j]==0){
                    a[i] = 1;
                    b[j] = 1;
                }
            }
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(a[i]==1||b[j]==1){
                    matrix[i][j]=0;
                }
            }
        }
    }
}

解答2:

//官方解答:预处理出两个标记变量,接着使用其他行与列去处理第一行与第一列,然后反过来使用第一行与第一列去更新其他行与列,最后使用两个标记变量更新第一行与第一列即可
class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean flagCol0 = false, flagRow0 = false;
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                flagCol0 = true;
            }
        }
        for (int j = 0; j < n; j++) {
            if (matrix[0][j] == 0) {
                flagRow0 = true;
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        if (flagCol0) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;
            }
        }
        if (flagRow0) {
            for (int j = 0; j < n; j++) {
                matrix[0][j] = 0;
            }
        }
    }
}

解答3:

//官方解答:对方法二进一步优化,只使用一个标记变量记录第一列是否原本存在 00。这样,第一列的第一个元素即可以标记第一行是否出现 00。但为了防止每一列的第一个元素被提前更新,我们需要从最后一行开始,倒序地处理矩阵元素。
class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean flagCol0 = false;
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                flagCol0 = true;
            }
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        for (int i = m - 1; i >= 0; i--) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
            if (flagCol0) {
                matrix[i][0] = 0;
            }
        }
    }
}

面试题 01.09. 字符串轮转

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

示例1:

 输入:s1 = "waterbottle", s2 = "erbottlewat"
 输出:True

示例2:

 输入:s1 = "aa", s2 = "aba"
 输出:False

提示:

  1. 字符串长度在[0, 100000]范围内。

说明:

  1. 你能只调用一次检查子串的方法吗?

解答:

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

面试题 02.01. 移除重复节点

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

示例1:

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

示例2:

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

提示:

  1. 链表长度在[0, 20000]范围内。
  2. 链表元素在[0, 20000]范围内。

进阶:

如果不得使用临时缓冲区,该怎么解决?

解答1:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeDuplicateNodes(ListNode head) {
        if(head==null) return head;
        ListNode t = head;
        Set<Integer> m = new HashSet<>();
        m.add(t.val);
        while(t.next!=null){
            if(m.add(t.next.val)){
                t = t.next;
            }else{
                t.next = t.next.next;
            }
        }
        return head;
    }
}

解答2:

//不得使用临时缓冲区,注意循环对比
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeDuplicateNodes(ListNode head) {
        if(head==null) return head;
        ListNode t = head;
        while(t!=null){
            ListNode node = t;
            while(node.next!=null){
                if(node.next.val==t.val){
                    node.next = node.next.next;
                }else{
                    node = node.next;
                }
            }
            t = t.next;
        }
        return head;
    }
}

面试题 02.02. 返回倒数第 k 个节点

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

**注意:**本题相对原题稍作改动

示例:

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

说明:

给定的 k 保证是有效的。

解答:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int kthToLast(ListNode head, int k) {
        ListNode l1 = head, l2 = head;
        while(k-->0){
            l2 = l2.next;
        }
        while(l2!=null){
            l1 = l1.next;
            l2 = l2.next;
        }
        return l1.val;
    }
}

面试题 02.03. 删除中间节点

若链表中的某个节点,既不是链表头节点,也不是链表尾节点,则称其为该链表的「中间节点」。

假定已知链表的某一个中间节点,请实现一种算法,将该节点从链表中删除。

例如,传入节点 c(位于单向链表 a->b->c->d->e->f 中),将其删除后,剩余链表为 a->b->d->e->f

示例:

输入:节点 5 (位于单向链表 4->5->1->9 中)
输出:不返回任何数据,从链表中删除传入的节点 5,使链表变为 4->1->9

解答:

//注意读题,该题直接给出中间节点,不需要先找后删
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

面试题 02.04. 分割链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你不需要 保留 每个分区中各节点的初始相对位置。

示例 1:

img

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

示例 2:

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

提示:

  • 链表中节点的数目在范围 [0, 200]
  • -100 <= Node.val <= 100
  • -200 <= x <= 200

解答:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode l1 = new ListNode(0);
        ListNode head1 = l1;
        ListNode l2 = new ListNode(0);
        ListNode head2 = l2;
        while(head!=null){
            if(head.val<x){
                l1.next = head;
                l1 = l1.next;
            }else{
                l2.next = head;
                l2 = l2.next;
            }
            head = head.next;
        }
        l2.next = null;
        l1.next = head2.next;
        return head1.next;
    }
}

面试题 02.05. 链表求和

给定两个用链表表示的整数,每个节点包含一个数位。

这些数位是反向存放的,也就是个位排在链表首部。

编写函数对这两个整数求和,并用链表形式返回结果。

示例:

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

**进阶:**思考一下,假设这些数位是正向存放的,又该如何解决呢?

示例:

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

解答:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode node = new ListNode(0);
        ListNode head = node;
        int temp = 0;
        while(l1!=null||l2!=null){
            int sum = temp;
            if(l1!=null){
                sum += l1.val;
                l1 = l1.next;
            }
            if(l2!=null){
                sum += l2.val;
                l2 = l2.next;
            }
            node.next = new ListNode(sum%10);
            node = node.next;
            temp = sum/10;
        }
        if(temp>0){
            node.next = new ListNode(1);
        }
        return head.next;
    }
}

面试题 02.06. 回文链表

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

示例 1:

输入: 1->2
输出: false 

示例 2:

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

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

解答:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode L = head, R = head, node = null;
        while(R!=null&&R.next!=null){
            ListNode temp = L;
            L = L.next;
            R = R.next.next;
            temp.next = node;
            node = temp;
        }
        if(R!=null){
            L = L.next;
        }
        while(L!=null){
            if(L.val!=node.val){
                return false;
            }
            L = L.next;
            node = node.next;
        }
        return true;
    }
}

面试题 02.07. 链表相交

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

图示两个链表在节点 c1 开始相交**:**

img

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

示例 1:

img

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

示例 2:

img

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

示例 3:

img

输入: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 。

提示:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 0 <= m, n <= 3 * 104
  • 1 <= Node.val <= 105
  • 0 <= skipA <= m
  • 0 <= skipB <= n
  • 如果 listAlistB 没有交点,intersectVal0
  • 如果 listAlistB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

**进阶:**你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?

解答1:

//计算两个链表长度,对齐后遍历
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null||headB==null) return null;
        ListNode l1 = headA, l2 = headB;
        int num1 = 0, num2 = 0;
        while(l1.next!=null){
            l1 = l1.next;
            num1++;
        }
        while(l2.next!=null){
            l2 = l2.next;
            num2++;
        }
        l1 = headA;
        l2 = headB;
        if(num1>num2){
            for(int i=0;i<num1-num2;i++){
                l1 = l1.next;
            }
        }else{
            for(int i=0;i<num2-num1;i++){
                l2 = l2.next;
            }
        }
        while(l1!=null){
            if(l1==l2){
                return l1;
            }
            l1 = l1.next;
            l2 = l2.next;
        }
        return null;
    }
}

解答2:

面试题 02.07. 链表相交(双指针,清晰图解) - 链表相交 - 力扣(LeetCode) (leetcode-cn.com)

/*
双指针进行遍历,每个指针先遍历自身,然后遍历另一条链表,因为遍历的都是两条链表之和,所以路径相同。如果相交,则会在第二次遍历时相遇,如果不相交,则会同时指向null。
*/
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode l1 = headA, l2 = headB;
        while (l1 != l2) {
            l1 = l1 != null ? l1.next : headB;
            l2 = l2 != null ? l2.next : headA;
        }
        return l2;
    }
}

面试题 02.08. 环路检测

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

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

示例 1:

img

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

示例 2:

img

输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

img

输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

进阶:

  • 你是否可以不用额外空间解决此题?

解答:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null) return null;
        ListNode l1 = head, l2 = head;
        while(l2!=null&&l2.next!=null){
            l1 = l1.next;
            l2 = l2.next.next;
            if(l1==l2){
                ListNode t = head;
                while(t!=l1){
                    t = t.next;
                    l1 = l1.next;
                }
                return t;
            }
        }
        return null;
    }
}

面试题 03.01. 三合一

三合一。描述如何只用一个数组来实现三个栈。

你应该实现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]

提示:

  • 0 <= stackNum <= 2

解答:

class TripleInOne {
    int n = 3;
    int[][] myStack;
    int[] index;

    public TripleInOne(int stackSize) {
        myStack = new int[n][stackSize];
        index = new int[n];
    }
    
    public void push(int stackNum, int value) {
        if(index[stackNum] < myStack[stackNum].length){
            myStack[stackNum][index[stackNum]] = value;
            index[stackNum]++;
        }
    }
    
    public int pop(int stackNum) {
        if(index[stackNum]>0){
            int value = myStack[stackNum][index[stackNum]-1];
            index[stackNum]--;
            return value;
        }else{
            return -1;
        }
    }
    
    public int peek(int stackNum) {
        if(index[stackNum]>0){
            return myStack[stackNum][index[stackNum]-1];
        }else{
            return -1;
        }
    }
    
    public boolean isEmpty(int stackNum) {
        return index[stackNum] == 0;
    }
}

/**
 * Your TripleInOne object will be instantiated and called as such:
 * TripleInOne obj = new TripleInOne(stackSize);
 * obj.push(stackNum,value);
 * int param_2 = obj.pop(stackNum);
 * int param_3 = obj.peek(stackNum);
 * boolean param_4 = obj.isEmpty(stackNum);
 */

面试题 03.02. 栈的最小值

请设计一个栈,除了常规栈支持的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 {

    /** initialize your data structure here. */
    Stack<Integer> s1;
    Stack<Integer> s2;

    public MinStack() {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    
    public void push(int x) {
        s1.push(x);
        if(s2.isEmpty()||x<s2.peek()){
            s2.push(x);
        }else{
            s2.push(s2.peek());
        }
    }
    
    public void pop() {
        s1.pop();
        s2.pop();
    }
    
    public int top() {
        return s1.peek();
    }
    
    public int getMin() {
        return s2.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

面试题 03.03. 堆盘子

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

当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,poppopAt 应返回 -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]

解答:

class StackOfPlates {

    List<Stack<Integer>> stackList;
    int n;

    public StackOfPlates(int cap) {
        stackList = new LinkedList<>();
        this.n = cap;
    }
    
    public void push(int val) {
        if(n<=0){
            return;
        }
        if(stackList.isEmpty()||stackList.get(stackList.size()-1).size()==n){
            Stack<Integer> s = new Stack<>();
            s.push(val);
            stackList.add(s);
            return;
        }
        stackList.get(stackList.size()-1).push(val);
    }
    
    public int pop() {
        return popAt(stackList.size()-1);
    }
    
    public int popAt(int index) {
        if(index<0||index>=stackList.size()){
            return -1;
        }
        Stack<Integer> s = stackList.get(index);
        int ans = s.pop();
        if(s.isEmpty()){
            stackList.remove(index);
        }
        return ans;
    }
}

/**
 * Your StackOfPlates object will be instantiated and called as such:
 * StackOfPlates obj = new StackOfPlates(cap);
 * obj.push(val);
 * int param_2 = obj.pop();
 * int param_3 = obj.popAt(index);
 */

面试题 03.04. 化栈为队

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

示例:

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

说明:

  • 你只能使用标准的栈操作 – 也就是只有 push to top, peek/pop from top, sizeis empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。

解答:

class MyQueue {

    /** Initialize your data structure here. */
    Stack<Integer> s1;
    Stack<Integer> s2;

    public MyQueue() {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    
    /** Push element x to the back of queue. */
    public void push(int x) {
        s1.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    public int pop() {
        if(s2.isEmpty()){
            while(!s1.isEmpty()){
                s2.push(s1.pop());
            }
        }
        return s2.pop();
    }
    
    /** Get the front element. */
    public int peek() {
        if(s2.isEmpty()){
            while(!s1.isEmpty()){
                s2.push(s1.pop());
            }
        }
        return s2.peek();
    }
    
    /** Returns whether the queue is empty. */
    public boolean empty() {
        return s1.isEmpty() && s2.isEmpty();
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

面试题 03.05. 栈排序

栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:pushpoppeekisEmpty。当栈为空时,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]

说明:

  1. 栈中的元素数目在[0, 5000]范围内。

解答1:

class SortedStack {

    Stack<Integer> s;
    public SortedStack() {
        s = new Stack<>();
    }
    
    public void push(int val) {
        if(s.isEmpty()){
            s.push(val);
        }else if(val <= s.peek()){
            s.push(val);
        }else{
            Stack<Integer> temp = new Stack<>();
            while(!s.isEmpty()&&val>s.peek()){
                temp.push(s.pop());
            }
            s.push(val);
            while(!temp.isEmpty()){
                s.push(temp.pop());
            }
        }
    }
    
    public void pop() {
        if(!s.isEmpty()){
            s.pop();
        }
    }
    
    public int peek() {
        return s.isEmpty()? -1: s.peek();
    }
    
    public boolean isEmpty() {
        return s.isEmpty();
    }
}

/**
 * Your SortedStack object will be instantiated and called as such:
 * SortedStack obj = new SortedStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.isEmpty();
 */

解答2:

//push结束后,不将辅助栈中的内容移回主栈,当需要pop时在进行操作。
class SortedStack {

    Stack<Integer> s;
    Stack<Integer> temp;
    public SortedStack() {
        s = new Stack<>();
        temp = new Stack<>();
    }
    
    public void push(int val) {
        int min = s.isEmpty() ? Integer.MAX_VALUE : s.peek();
        int max = temp.isEmpty() ? Integer.MIN_VALUE : temp.peek();
        while(true){
            if(val>min){
                temp.push(s.pop());
                min = s.isEmpty() ? Integer.MAX_VALUE : s.peek();
            }else if(val<max){
                s.push(temp.pop());
                max = temp.isEmpty() ? Integer.MIN_VALUE : temp.peek();
            }else{
                s.push(val);
                break;
            }
        }
    }
    
    public void pop() {
        while (!temp.isEmpty()) {
            s.push(temp.pop());
        }
        if(!s.isEmpty()){
            s.pop();
        }
    }
    
    public int peek() {
        while (!temp.isEmpty()) {
            s.push(temp.pop());
        }
        return s.isEmpty()? -1: s.peek();
    }
    
    public boolean isEmpty() {
        return s.isEmpty() && temp.isEmpty();
    }
}

/**
 * Your SortedStack object will be instantiated and called as such:
 * SortedStack obj = new SortedStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.isEmpty();
 */

面试题 03.06. 动物收容所

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

说明:

  1. 收纳所的最大容量为20000

解答:

class AnimalShelf {

    List<int[]> list;
    public AnimalShelf() {
        list = new LinkedList<>();
    }
    
    public void enqueue(int[] animal) {
        list.add(animal);
    }
    
    public int[] dequeueAny() {
        if(list.size()!=0){
            int[] t = list.get(0);
            list.remove(0);
            return t;
        }
        return new int[]{-1,-1};
    }
    
    public int[] dequeueDog() {
        for(int i=0;i<list.size();i++){
            int[] t = list.get(i);
            if(t[1]==1){
                list.remove(i);
                return t;
            }
        }
        return new int[]{-1,-1};
    }
    
    public int[] dequeueCat() {
        for(int i=0;i<list.size();i++){
            int[] t = list.get(i);
            if(t[1]==0){
                list.remove(i);
                return t;
            }
        }
        return new int[]{-1,-1};
    }
}

/**
 * Your AnimalShelf object will be instantiated and called as such:
 * AnimalShelf obj = new AnimalShelf();
 * obj.enqueue(animal);
 * int[] param_2 = obj.dequeueAny();
 * int[] param_3 = obj.dequeueDog();
 * int[] param_4 = obj.dequeueCat();
 */

面试题 04.01. 节点间通路

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

示例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

提示:

  1. 节点数量n在[0, 1e5]范围内。
  2. 节点编号大于等于 0 小于 n。
  3. 图中可能存在自环和平行边。

解答:

class Solution {
    private boolean[] visited = null;
    public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {
        this.visited = new boolean[graph.length];
        return helper(graph, start, target);
    }
    private boolean helper(int[][] graph, int start, int target){
        for(int i=0;i<graph.length;i++){
            if(!visited[i]){
                if(graph[i][0]==start&&graph[i][1]==target){
                    return true;
                }
                visited[i] = true;
                if(graph[i][1]==target&&helper(graph,start,graph[i][0])){
                    return true;
                }
                visited[i] = false;
            }
        }
        return false;
    }
}

面试题 04.02. 最小高度树

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

示例:

给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:             0 
         / \ 
       -3   9 
       /   / 
     -10  5 

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return fun(nums, 0, nums.length-1);
    }
    public static TreeNode fun(int[] nums, int L, int R){
        if(L>R){
            return null;
        }
        int mid = L + (R-L)/2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = fun(nums, L, mid-1);
        root.right = fun(nums, mid+1, R);
        return root;
    }
}

面试题 04.03. 特定深度节点链表

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

示例:

输入:[1,2,3,4,5,null,7,8]

        1
       /  \ 
      2    3
     / \    \ 
    4   5    7
   /
  8

输出:[[1],[2,3],[4,5,7],[8]]

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode[] listOfDepth(TreeNode tree) {
        LinkedList<TreeNode> list = new LinkedList<>();
        ArrayList<ListNode> ans = new ArrayList<>();
        list.add(tree);
        while(!list.isEmpty()){
            ListNode head = new ListNode(0);
            ListNode temp = head;
            int n = list.size();
            for(int i=0;i<n;i++){
                TreeNode node = list.poll();
                if(node.left!=null){
                    list.add(node.left);
                }
                if(node.right!=null){
                    list.add(node.right);
                }
                temp.next = new ListNode(node.val);
                temp = temp.next;
            }
            ans.add(head.next);
        }
        return ans.toArray(new ListNode[ans.size()]);
    }
}

面试题 04.04. 检查平衡性

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

给定二叉树 [3,9,20,null,null,15,7] 
    3
   / \
  9  20
    /  \
   15   7
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]
      1
     / \
    2   2
   / \
  3   3
 / \
4   4
返回 false 。

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isBalanced(TreeNode root) {
        return fun(root)!=-1;
    }

    public int fun(TreeNode root){
        if(root==null){
            return 0;
        }
        int L = fun(root.left);
        int R = fun(root.right);
        if(L==-1||R==-1||Math.abs(L-R)>1){
            return -1;
        }else{
            return Math.max(L, R) + 1;
        }
    }
}

面试题 04.05. 合法二叉搜索树

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

示例 1:

输入:    2   / \  1   3输出: true

示例 2:

输入:    5   / \  1   4     / \    3   6输出: false解释: 输入为: [5,1,4,null,null,3,6]。     根节点的值为 5 ,但是其右子节点值为 4 。

解答1:

//递归,始终维护一个范围min-max,不在范围内则不是二叉搜索树。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        return fun(root,null,null);
    }
    public boolean fun(TreeNode root, Integer min, Integer max){
        if(root==null){
            return true;
        }
        int val = root.val;
        if(min!=null && val<=min){
            return false;
        }
        if(max!=null && val>=max){
            return false;
        }
        if(!fun(root.left, min, val)){
            return false;
        }
        if(!fun(root.right, val, max)){
            return false;
        }
        return true;
    }
}

解答2:

//中序遍历,若非递增,则不是二叉搜索树。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        Deque<TreeNode> dq = new LinkedList<>();
        double temp = -Double.MAX_VALUE;
        while(!dq.isEmpty()||root!=null){
            while(root!=null){
                dq.push(root);
                root = root.left;
            }
            root = dq.pop();
            if(root.val<=temp){
                return false;
            }
            temp = root.val;
            root = root.right;
        }
        return true;
    }
}

面试题 04.06. 后继者

设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。

如果指定节点没有对应的“下一个”节点,则返回null

示例 1:

输入: root = [2,1,3], p = 1

  2
 / \
1   3

输出: 2

示例 2:

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

      5
     / \
    3   6
   / \
  2   4
 /   
1

输出: null

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        Deque<TreeNode> dq = new LinkedList<>();
        boolean flag = false;
        while(!dq.isEmpty()||root!=null){
            while(root!=null){
                dq.push(root);
                root = root.left;
            }
            root = dq.pop();
            if(flag){
                return root;
            }
            if(root == p){
                flag = true;
            }
            root = root.right;
        }
        return null;
    }
}

面试题 04.08. 首个共同祖先

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

    3
   / \
  5   1
 / \ / \
6  2 0  8
  / \
 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 为不同节点且均存在于给定的二叉树中。

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null){
            return null;
        }
        if(root==p||root==q){
            return root;
        }
        TreeNode L = lowestCommonAncestor(root.left, p, q);
        TreeNode R = lowestCommonAncestor(root.right, p, q);
        if(L!=null&&R!=null){
            return root;
        }
        return (L!=null)?L:R;
    }
}

面试题 04.09. 二叉搜索树序列

从左向右遍历一个数组,通过不断将其中的元素插入树中可以逐步地生成一棵二叉搜索树。

给定一个由不同节点组成的二叉搜索树 root,输出所有可能生成此树的数组。

示例 1:

输入: root = [2,1,3]
输出: [[2,1,3],[2,3,1]]
解释: 数组 [2,1,3]、[2,3,1] 均可以通过从左向右遍历元素插入树中形成以下二叉搜索树
       2 
      / \ 
        3

示例 2:

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

提示:

  • 二叉搜索树中的节点数在 [0, 1000] 的范围内
  • 1 <= 节点值 <= 10^6
  • 用例保证符合要求的数组数量不超过 5000

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    private List<List<Integer>> ans;
    public List<List<Integer>> BSTSequences(TreeNode root) {
        ans = new ArrayList<>();
        List<Integer> temp = new ArrayList<>();
        if(root==null){
            ans.add(temp);
            return ans;
        }
        List<TreeNode> dq = new ArrayList<>();
        dq.add(root);
        bfs(dq, temp);
        return ans;
    }
    public void bfs(List<TreeNode> dq, List<Integer> temp){
        if(dq.isEmpty()){
            ans.add(new ArrayList<>(temp));
            return;
        }
        List<TreeNode> list = new ArrayList<>(dq);
        for(int i=0;i<dq.size();i++){
            TreeNode node = dq.get(i);
            temp.add(node.val);
            dq.remove(i);
            if(node.left!=null) dq.add(node.left);
            if(node.right!=null) dq.add(node.right);
            bfs(dq, temp);
            temp.remove(temp.size()-1);
            dq = new ArrayList<>(list);
        }
    }
}

面试题 04.10. 检查子树

检查子树。你有两棵非常大的二叉树: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

提示:

  1. 树的节点数目范围为[0, 20000]。

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private boolean isSame(TreeNode t1, TreeNode t2) {
        if (t1 == null && t2 == null) {
            return true;
        }
        if (t1 == null || t2 == null) {
            return false;
        }
        return t1.val == t2.val && isSame(t1.left, t2.left) && isSame(t1.right, t2.right);
    }

    public boolean checkSubTree(TreeNode t1, TreeNode t2) {
        if (t1 == null) {
            return t2 == null;
        }
        return isSame(t1, t2) || checkSubTree(t1.left, t2) || checkSubTree(t1.right, t2);
    }
}

面试题 04.12. 求和路径

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

示例:
给定如下二叉树,以及目标和 sum = 22

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1

返回:

3
解释:和为 22 的路径有:[5,4,11,2], [5,8,4,5], [4,11,7]

提示:

  • 节点总数 <= 10000

解答:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    HashMap<Integer, Integer> m; 
    int ans = 0;
    public int pathSum(TreeNode root, int sum) {
        m = new HashMap<>();
        m.put(0,1);
        dfs(root, sum, 0);
        return ans;
    }
    public void dfs(TreeNode root, int sum, int temp) {
        if(root==null){
            return;
        }
        temp += root.val;
        ans += m.getOrDefault(temp-sum,0);
        m.put(temp,m.getOrDefault(temp,0)+1);
        dfs(root.left,sum,temp);
        dfs(root.right,sum,temp);
        m.put(temp,m.getOrDefault(temp,0)-1);
    }
}

面试题 05.01. 插入

给定两个整型数字 NM,以及表示比特位置的 iji <= j,且从 0 位开始计算)。

编写一种方法,使 M 对应的二进制数字插入 N 对应的二进制数字的第 i ~ j 位区域,不足之处用 0 补齐。具体插入过程如图所示。

img

题目保证从 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) {
        int L = N>>j>>1;
        L = L<<j<<1;
        int mid = M<<i;
        int R = N&((1<<i)-1);
        return L|mid|R;
    }
}

面试题 05.02. 二进制数转字符串

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

示例1:

 输入:0.625
 输出:"0.101"

示例2:

 输入:0.1
 输出:"ERROR"
 提示:0.1无法被二进制准确表示

提示:

  1. 32位包括输出中的"0."这两位。

解答:

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

面试题 05.03. 翻转数位

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

示例 1:

输入: num = 1775(110111011112)
输出: 8

示例 2:

输入: num = 7(01112)
输出: 4

解答:

class Solution {
    public int reverseBits(int num) {
        int L=0, R=0, ans=0;
        for(int i=0;i<32;i++){
            if((num&1)==1){
                R++;
            }else{
                L=R;
                R=0;
            }
            ans = Math.max(L+R+1,ans);
            num >>= 1;
        }
        return ans==33 ? 32:ans;
    }
}

面试题 05.04. 下一个数

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

示例1:

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

示例2:

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

提示:

  1. num的范围在[1, 2147483647]之间;
  2. 如果找不到前一个或者后一个满足条件的正数,那么输出 -1。

解答:

class Solution {
    public int[] findClosedNumbers(int num) {
       if(num==Integer.MAX_VALUE){
           return new int[]{-1,-1};
       }
       int[] ans=new int[2];
       ans[0]=getBig(num);
       ans[1]=getSmall(num);
       return ans;
    }



//找到第一个01变成10
//最简单情况 ...00000111 从低位到高位,找到第一个01变成10
//复杂情况   ...00111000 找到第一个01变成10以后,把低位的1右移
    public int getBig(int num){
        int cnt=0;
        //去掉低位开始的0
        if((num&(1<<cnt))==0){
            while(cnt<31&&(num&(1<<cnt))==0){
                cnt++;
            }
        }
        //此时cnt为从低位往高位数第一个1的位置
        int c1=cnt;
       
        while(cnt<31&&(num&(1<<cnt))>0){
                cnt++;
        } 

        if(cnt==31){
            return -1;
        }
  
        num+=(1<<cnt);//    ...00111000 
        cnt--;
        num-=(1<<cnt);//变为   01011000
        
        cnt--;
        //cnt指向要右移的第一个1
        int count=0;
        //c1大于0才需要右移动
        while(c1>0&&cnt>0&&( num&(1<<cnt) )>0){  
            num-=(1<<cnt);
            count++;
            cnt--;
        }
        //把右边清0
        while(count>0){
            //count代表几个1      如0000 0011
            count--;
            num+=(1<<count);
            
        }
        return num;
    }

//找到第一个10变成01
//最简单情况 ...1110000 从低位到高位,找到第一个10变成01
//复杂情况   ...110011 找到第一个10变成01以后,把低位的1右移
    public int getSmall(int num){
        int cnt=0;
        //去掉开始的1
        if((num&(1<<cnt))>0){
            while(cnt<31&& (num & (1<<cnt))>0){
                cnt++;
            }
        }
        //此时cnt为从低位到高位第一个0的位置
        int c0=cnt;
       

        while(cnt<31&&(num&(1<<cnt))==0){
                cnt++;
        } 

        if(cnt==31){
            return -1;
        }

        
        //1000111
        //c0就是右边1的个数,要左移
        //0100111左移动1位,2=cnt-index
        num-=(1<<cnt);
        cnt--;
        num+=(1<<(cnt));//1000111-》0100111

        int cha=cnt-c0;//cnt指向从低位到高位第一个一串0后面的1 cha="差"是要左移的位数
        while(cnt-c0>0){
            cnt--;
            num+=(1<<cnt);
        }
        //把最右边清0
        while(cha>0){
            cha--;
            num-=(1<<cha);
            
        }
        return num;
    }
}

面试题 05.06. 整数转换

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

示例1:

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

示例2:

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

提示:

  1. A,B范围在[-2147483648, 2147483647]之间

解答:

class Solution {
    public int convertInteger(int A, int B) {
        int t = A^B;
        int ans = 0;
        while(t!=0){
            t = t&(t-1);
            ans++;
        }
        return ans;
    }
}

面试题 05.07. 配对交换

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

示例1:

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

示例2:

 输入:num = 3
 输出:3

提示:

  1. num的范围在[0, 2^30 - 1]之间,不会发生整数溢出。

解答1:

class Solution {
    public int exchangeBits(int num) {
        int i=0, j=1;
        while(i<=30){
            int a = num>>i&1, b = num>>j&1;
            if(a!=b){
                num ^= 1<<i;
                num ^= 1<<j;
            }
            i += 2;
            j += 2;
        }
        return num;
    }
}

解答2:

/*
0x55555555 = 0b0101_0101_0101_0101_0101_0101_0101_0101
0xaaaaaaaa = 0b1010_1010_1010_1010_1010_1010_1010_1010

用这两个数做与运算,就可以把奇数位和偶数位取出来,
然后位左移奇数位,右移偶数位,
再把 奇数位和偶数位做或运算。
*/
class Solution {
    public int exchangeBits(int num) {
        //奇数
        int odd = num & 0x55555555;
        //偶数
        int even = num & 0xaaaaaaaa;
        odd = odd << 1;
        even = even >>> 1; 
        return odd|even;
    }
}

面试题 05.08. 绘制直线

已知一个由像素点组成的单色屏幕,每行均有 w 个像素点,所有像素点初始为 0,左上角位置为 (0,0)

现将每行的像素点按照「每 32 个像素点」为一组存放在一个 int 中,再依次存入长度为 length 的一维数组中。

我们将在屏幕上绘制一条从点 (x1,y) 到点 (x2,y) 的直线(即像素点修改为 1),请返回绘制过后的数组。

注意:

  • 用例保证屏幕宽度 w 可被 32 整除(即一个 int 不会分布在两行上)

示例1:

 输入:length = 1, w = 32, x1 = 30, x2 = 31, y = 0
 输出:[3]
 解释:在第 0 行的第 30 位到第 31 位画一条直线,屏幕二进制形式表示为 [00000000000000000000000000000011],因此返回 [3]

示例2:

 输入:length = 3, w = 96, x1 = 0, x2 = 95, y = 0
 输出:[-1, -1, -1]
 解释:由于二进制 11111111111111111111111111111111 的 int 类型代表 -1,因此返回 [-1,-1,-1]

提示:

  • 1 <= length <= 10^5
  • 1 <= w <= 3 * 10^5
  • 0 <= x1 <= x2 < w
  • 0 <= y <= 10

解答:

class Solution {
    public int[] drawLine(int length, int w, int x1, int x2, int y) {
        int[] ans = new int[length];
        int n = y*w/32;
        int L = x1/32+n, R = x2/32+n;
        for(int i=L;i<=R;i++){
            ans[i] = -1;
        }
        ans[L] = ans[L]&-1>>>x1%32;
        ans[R] = ans[R]&Integer.MIN_VALUE>>x2%32;
        return ans;
    }
}

面试题 08.01. 三步问题

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

示例1:

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

示例2:

 输入:n = 5
 输出:13

提示:

  1. n范围在[1, 1000000]之间

解答1:

class Solution {
    public int waysToStep(int n) {
        if(n<=2) return n;
        int[] ans = new int[n+1];
        ans[1] = 1;
        ans[2] = 2;
        ans[3] = 4;
        for(int i=4;i<=n;i++){
            ans[i] = ((ans[i-1]+ans[i-2])%1000000007+ans[i-3])%1000000007;
        }
        return ans[n];
    }
}

面试题 08.02. 迷路的机器人

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

img

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

返回一条可行的路径,路径由经过的网格的行号和列号组成。左上角为 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列(右下角)

**说明:**rc 的值均不超过 100。

解答:

class Solution {
    int m, n;
    int[][] map;
    boolean[][] visited;
    public List<List<Integer>> pathWithObstacles(int[][] obstacleGrid) {
        n = obstacleGrid.length;
        m = obstacleGrid[0].length;
        map = obstacleGrid;
        visited = new boolean[n][m];
        List<List<Integer>> ans = new LinkedList<>();
        dfs(0,0,ans);
        return ans;
    }
    public boolean dfs(int x, int y, List<List<Integer>> ans){
        if(x>=n||y>=m||map[x][y]==1||visited[x][y]){
            return false;
        }
        ans.add(Arrays.asList(x,y));
        if(x==n-1&&y==m-1){
            return true;
        }
        visited[x][y] = true;
        if(dfs(x+1,y,ans)||dfs(x,y+1,ans)){
            return true;
        }
        ans.remove(ans.size()-1);
        return false;
    }
}

面试题 08.03. 魔术索引

魔术索引。 在数组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

说明:

  1. nums长度在[1, 1000000]之间
  2. 此题为原书中的 Follow-up,即数组中可能包含重复元素的版本

解答1:

//适用于没有负数的场景,因为nums[i]一定大于等于i。但存在负数是,i只能进行i++,速度较慢,使用下面的二分查找。
class Solution {
    public int findMagicIndex(int[] nums) {
        int i=0;
        while(i<nums.length){
            if(nums[i]==i){
                return i;
            }else if(nums[i]>i){
                i = nums[i];
            }else{
                i++;
            }
        }
        return -1;
    }
}

解答2:

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

    public int fun(int[] nums, int L, int R) {
        if (L > R) {
            return -1;
        }
        int mid = L+(R-L)/2;
        int ans = fun(nums, L, mid-1);
        if (ans != -1) {
            return ans;
        } else if (nums[mid] == mid) {
            return mid;
        }
        return fun(nums, mid+1, R);
    }
}

面试题 08.04. 幂集

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

说明:解集不能包含重复的子集。

示例:

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

解答:

class Solution {
    List<List<Integer>> ans;
    public List<List<Integer>> subsets(int[] nums) {
        ans = new ArrayList<>();
        dfs(nums, nums.length-1, new ArrayList<Integer>());
        return ans;
    }
    public void dfs(int[] nums, int n, List<Integer> temp){
        if(n<0){
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        temp.add(nums[n]);
        dfs(nums,n-1,temp);
        temp.remove(temp.size()-1);
        dfs(nums,n-1,temp);
    }
}

面试题 08.05. 递归乘法

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

示例1:

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

示例2:

 输入:A = 3, B = 4
 输出:12

提示:

  1. 保证乘法范围不会溢出

解答1:

class Solution {
    public int multiply(int A, int B) {
        if(A>B) return multiply(B,A);
        int ans = 0;
        for(int i=0;i<A;i++){
            ans += B;
        }
        return ans;
    }
}

解答2:

//位运算
class Solution {
    public int multiply(int A, int B) {
        if(A>B){
            return multiply(B,A);
        }
        if(A==1){
            return B;
        }
        if(A%2==0){
            return multiply(A>>1,B)<<1;
        }
        else{
            return (multiply(A>>1,B)<<1)+B;
        }
    }
}

面试题 08.06. 汉诺塔问题

在经典汉诺塔问题中,有 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]

提示:

  1. A中盘子的数目不大于14个。

解答:

class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        fun(A.size(),A,B,C);
    }
    public void fun(int size,List<Integer> start,List<Integer> auxiliary,List<Integer> target){
        //只剩一个盘子时,直接将它从第一个柱子移动到第三个柱子
        if(size == 1){
            target.add(start.remove(start.size()-1));
            return;
        }
        //首先将 n-1 个盘子,从第一个柱子移动到第二个柱子
        fun(size-1, start, target, auxiliary);
        //然后将最后一个盘子移动到第三个柱子上
        target.add(start.remove(start.size()-1));
        //最后将第二个柱子上的 n-1 个盘子,移动到第三个柱子上
        fun(size-1, auxiliary, start, target);
    }
}

面试题 08.07. 无重复字符串的排列组合

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

示例1:

 输入:S = "qwe"
 输出:["qwe", "qew", "wqe", "weq", "ewq", "eqw"]

示例2:

 输入:S = "ab"
 输出:["ab", "ba"]

提示:

  1. 字符都是英文字母。
  2. 字符串长度在[1, 9]之间。

解答1:

class Solution {
    List<String> ans= new ArrayList<>();
    StringBuffer s =new StringBuffer();
    public String[] permutation(String S) {
        dfs(S);
        return ans.toArray(new String[ans.size()]);
    }
    public void dfs(String S){
        if(s.length()==S.length()){
            ans.add(new String(s));
            return;
        }
        for(int i=0;i<S.length();i++){
            String temp = new String(s);
            if(temp.contains(S.charAt(i)+"")){
                continue;
            }
            s.append(S.charAt(i));
            dfs(S);
            s.deleteCharAt(s.length()-1);
        }
    }
}

解答2:

class Solution {
    List<String> list = new ArrayList<>();

    public String[] permutation(String S) {
        permutate(S.toCharArray(), 0);
        String[] res = new String[list.size()];
        for (int i = 0; i < res.length; i++) {
            res[i] = list.get(i);
        }
        return res;
    }

    public void permutate(char[] arr, int first) {
        if (first == arr.length - 1) {
            list.add(new String(arr));
            return;
        }
        for (int i = first; i < arr.length; i++) {
            swap(arr, first, i);
            permutate(arr, first + 1);
            swap(arr, first, i);
        }
    }

    public void swap(char[] arr, int i, int j) {
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

面试题 08.08. 有重复字符串的排列组合

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

示例1:

 输入:S = "qqe"
 输出:["eqq","qeq","qqe"]

示例2:

 输入:S = "ab"
 输出:["ab", "ba"]

提示:

  1. 字符都是英文字母。
  2. 字符串长度在[1, 9]之间。

解答:

class Solution {
    List<String> ans = new ArrayList<>();
    StringBuffer s = new StringBuffer();
    boolean[] visited;
    public String[] permutation(String S) {
        visited = new boolean[S.length()];
        char[] c = S.toCharArray();
        Arrays.sort(c);
        dfs(c);
        return ans.toArray(new String[ans.size()]);
    }
    public void dfs(char[] c){
        if(s.length()==c.length){
            ans.add(s.toString());
            return;
        }
        for(int i=0;i<c.length;i++){
            if(visited[i]){
                continue;
            }
            visited[i]=true;
            s.append(c[i]);
            dfs(c);
            s.deleteCharAt(s.length()-1);
            visited[i]=false;
            while(i+1<c.length&&c[i]==c[i+1]){
                i++;
            }
        }
    }
}

面试题 08.09. 括号

括号。设计一种算法,打印n对括号的所有合法的(例如,开闭一一对应)组合。

说明:解集不能包含重复的子集。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

解答:

class Solution {
    List<String> ans;
    StringBuffer s;
    public List<String> generateParenthesis(int n) {
        ans = new ArrayList<>();
        s = new StringBuffer();
        dfs(n, n);
        return ans;
    }
    public void dfs(int L, int R){
        if(L==0&&R==0){
            ans.add(s.toString());
            return;
        }
        if(L<0||R<0||R<L){
            return;
        }
        s.append("(");
        dfs(L-1, R);
        s.deleteCharAt(s.length()-1);
        s.append(")");
        dfs(L, R-1);
        s.deleteCharAt(s.length()-1);
    }
}

面试题 08.10. 颜色填充

编写函数,实现许多图片编辑软件都支持的「颜色填充」功能。

待填充的图像用二维数组 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 ,因为它不属于初始坐标点的周围区域。

提示:

  • imageimage[0] 的长度均在范围 [1, 50] 内。
  • 初始坐标点 (sr,sc) 满足 0 <= sr < image.length0 <= sc < image[0].length
  • image[i][j]newColor 表示的颜色值在范围 [0, 65535] 内。

解答:

class Solution {
    int[] xx = {1,-1,0,0}, yy = {0,0,1,-1};
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        if(image[sr][sc]==newColor) return image;
        dfs(image, sr, sc, newColor, image[sr][sc]);
        return image;
    }

    public void dfs(int[][] image, int sr, int sc, int newColor, int oldColor) {
        if(image[sr][sc]==oldColor){
            image[sr][sc] = newColor;
            for(int i=0;i<4;i++){
                int x = sr+xx[i], y = sc+yy[i];
                if(x>=0&&x<image.length&&y>=0&&y<image[0].length){
                    dfs(image, x, y, newColor, oldColor);
                }
            }
        }
    }
}

面试题 08.11. 硬币

硬币。给定数量不限的硬币,币值为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

说明:

注意:

你可以假设:

  • 0 <= n (总金额) <= 1000000

解答:

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

面试题 08.12. 八皇后

设计一种算法,打印 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 {
    int[] temp;
    int len;
    List<List<String>> ans;
    List<String> list;
    
    public List<List<String>> solveNQueens(int n) {
        len = n;
        temp = new int[n];
        ans = new ArrayList<>();
        list = new ArrayList<>();
        dfs(0);
        return ans;
    }

    public void dfs(int row){
        if(row==len){
            for(int i=0;i<len;i++){
                char[] c = new char[len];
                Arrays.fill(c,'.');
                c[temp[i]] = 'Q';
                list.add(new String(c));
            }
            ans.add(new ArrayList<>(list));
            list = new ArrayList();
            return;
        }
        for(int i=0;i<len;i++){
            if(fun(row,i)){
                temp[row]=i;
                dfs(row+1);
            }
        }
    }

    public boolean fun(int row, int col){
        for(int i=0;i<row;i++){
            if(temp[i]==col) return false;
            if(row-i==Math.abs(col-temp[i])) return false;
        }
        return true;
    }
}

面试题 08.13. 堆箱子

堆箱子。给你一堆n个箱子,箱子宽 wi、深 di、高 hi。箱子不能翻转,将箱子堆起来时,下面箱子的宽度、高度和深度必须大于上面的箱子。实现一种方法,搭出最高的一堆箱子。箱堆的高度为每个箱子高度的总和。

输入使用数组[wi, di, hi]表示每个箱子。

示例1:

 输入:box = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
 输出:6

示例2:

 输入:box = [[1, 1, 1], [2, 3, 4], [2, 6, 7], [3, 4, 5]]
 输出:10

提示:

  1. 箱子的数目不大于3000个。

解答:

class Solution {
    public int pileBox(int[][] box) {
        Arrays.sort(box, (a,b)->{return a[0]-b[0];});
        int [][]DP = new int[box.length][2];
        DP[0][0] = 0; DP[0][1] = box[0][2];
        for(int i=1;i<box.length;i++){
            int []temp = box[i]; int max = temp[2];
            for(int j=0;j<i;j++){
                int []pre = box[j];
                if(temp[0]>pre[0] && temp[1]>pre[1] && temp[2]>pre[2]){
                    int val = temp[2]+DP[j][1];
                    if(val>max) max = val;
                }
            }
            DP[i][1] = max; DP[i][0] = Math.max(DP[i-1][0],DP[i-1][1]);
        }
        return Math.max(DP[box.length-1][0], DP[box.length-1][1]);
    }
}

面试题 08.14. 布尔运算

给定一个布尔表达式和一个期望的布尔结果 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

提示:

  • 运算符的数量不超过 19 个

解答:

class Solution {
    Integer[][][] memo;
    public int countEval(String s, int result) {
        int n = s.length();
        memo = new Integer[n][n][2];
        return dfs(0, n - 1, s, result);
    }
    int dfs(int l, int r, String s, int result) { //区间[l, r]求result的括号方案数
        if (l > r) return 0;
        if (l == r) {
            return (s.charAt(l) - '0') == result ? 1 : 0;
        }
        if (memo[l][r][result] != null) return memo[l][r][result];
        int ans = 0;
        for (int i = l; i <= r; i++) {
            char c = s.charAt(i);
            if (result == 0) {
                if (c == '&') ans += dfs(l, i - 1, s, 0) * dfs(i + 1, r, s, 0) + dfs(l, i - 1, s, 0) * dfs(i + 1, r, s, 1) + dfs(l, i - 1, s, 1) * dfs(i + 1, r, s, 0); //00、01、10
                if (c == '|') ans += dfs(l, i - 1, s, 0) * dfs(i + 1, r, s, 0); //00
                if (c == '^') ans += dfs(l, i - 1, s, 0) * dfs(i + 1, r, s, 0) + dfs(l, i - 1, s, 1) * dfs(i + 1, r, s, 1); //00、11
            } else {
                if (c == '&') ans += dfs(l, i - 1, s, 1) * dfs(i + 1, r, s, 1); //11
                if (c == '|') ans += dfs(l, i - 1, s, 0) * dfs(i + 1, r, s, 1) + dfs(l, i - 1, s, 1) * dfs(i + 1, r, s, 0) + dfs(l, i - 1, s, 1) * dfs(i + 1, r, s, 1); //01、10、11
                if (c == '^') ans += dfs(l, i - 1, s, 0) * dfs(i + 1, r, s, 1) + dfs(l, i - 1, s, 1) * dfs(i + 1, r, s, 0); //10、01
            }
        }
        return memo[l][r][result] = ans;
    }
}

面试题 10.01. 合并排序的数组

给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。

初始化 A 和 B 的元素数量分别为 mn

示例:

输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]

说明:

  • A.length == n + m

解答:

class Solution {
    public void merge(int[] A, int m, int[] B, int n) {
       int L = m-1, R = n-1;
       int t = m+n-1;
       while(L>=0||R>=0){
           if(L==-1){
               A[t--] = B[R--];
           }else if(R==-1){
               A[t--] = A[L--];
           }else if(A[L]>B[R]){
               A[t--] = A[L--];
           }else{
               A[t--] = B[R--];
           }
       }
    }
}

面试题 10.02. 变位词组

编写一种方法,对字符串数组进行排序,将所有变位词组合在一起。变位词是指字母相同,但排列不同的字符串。

**注意:**本题相对原题稍作修改

示例:

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

说明:

  • 所有输入均为小写字母。
  • 不考虑答案输出的顺序。

解答:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> m = new HashMap<>();
        for(String s:strs){
            char[] c = s.toCharArray();
            Arrays.sort(c);
            String t = new String(c);
            List<String> list = m.getOrDefault(t, new ArrayList<String>());
            list.add(s);
            m.put(t, list);
        }
        return new ArrayList<List<String>>(m.values());
    }
}

面试题 10.03. 搜索旋转数组

搜索旋转数组。给定一个排序后的数组,包含n个整数,但这个数组已被旋转过很多次了,次数不详。请编写代码找出数组中的某个元素,假设数组元素原先是按升序排列的。若有多个相同元素,返回索引值最小的一个。

示例1:

 输入: arr = [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14], target = 5
 输出: 8(元素5在该数组中的索引)

示例2:

 输入:arr = [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14], target = 11
 输出:-1 (没有找到)

提示:

  1. arr 长度范围在[1, 1000000]之间

解答:

class Solution {
    public int search(int[] arr, int target) {
        if(arr[0]==target) return 0;
        int L = 0, R = arr.length-1;
        while(L<=R){
            int mid = L+(R-L)/2;
            if(arr[mid]==target){
                while(mid>0&&arr[mid-1]==arr[mid]) mid--;
                return mid;
            }
            if(arr[mid]<arr[R]){
                if(arr[mid]<target&&target<=arr[R]){
                    L = mid+1;
                }else{
                    R = mid-1;
                }
            }else if(arr[mid]>arr[R]){
                if(target<arr[mid]&&target>=arr[L]){
                    R = mid-1;
                }else{
                    L = mid+1;
                }
            }else{
                R--;
            }
        }
        return -1;
    }
}

面试题 10.05. 稀疏数组搜索

稀疏数组搜索。有个排好序的字符串数组,其中散布着一些空字符串,编写一种方法,找出给定字符串的位置。

示例1:

 输入: words = ["at", "", "", "", "ball", "", "", "car", "", "","dad", "", ""], s = "ta"
 输出:-1
 说明: 不存在返回-1。

示例2:

 输入:words = ["at", "", "", "", "ball", "", "", "car", "", "","dad", "", ""], s = "ball"
 输出:4

提示:

  1. words的长度在[1, 1000000]之间

解答:

class Solution {
    public int findString(String[] words, String s) {
        int n = words.length;
        int L=0, R=n-1;
        while(L<=R){
            while(L<=R&&words[L].equals("")) L++;
            while(L<=R&&words[R].equals("")) R--;
            int mid = L+(R-L)/2;
            while(mid<=R&&words[mid].equals("")) mid++;
            if(words[mid].equals(s)){
                return mid;
            }
            if(words[mid].compareTo(s)>0){
                R = mid-1;
            }else{
                L = mid+1;
            }
        }
        return -1;
    }
}

面试题 10.09. 排序矩阵查找

给定M×N矩阵,每一行、每一列都按升序排列,请编写代码找出某元素。

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true

给定 target = 20,返回 false

解答:

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix==null||matrix.length==0) return false;
        return dfs(matrix, 0, matrix[0].length-1, target);
    }

    public boolean dfs(int[][] matrix, int r, int c, int target){
        if(r<0||c<0||r>=matrix.length||c>=matrix[0].length) return false;
        if(target==matrix[r][c]) return true;
        return target<matrix[r][c] ? dfs(matrix, r, c-1, target) : dfs(matrix, r+1, c, target);
    }
}

面试题 10.10. 数字流的秩

假设你正在读取一串整数。每隔一段时间,你希望能找出数字 x 的秩(小于或等于 x 的值的个数)。请实现数据结构和算法来支持这些操作,也就是说:

实现 track(int x) 方法,每读入一个数字都会调用该方法;

实现 getRankOfNumber(int x) 方法,返回小于或等于 x 的值的个数。

**注意:**本题相对原题稍作改动

示例:

输入:
["StreamRank", "getRankOfNumber", "track", "getRankOfNumber"]
[[], [1], [0], [0]]
输出:
[null,0,null,1]

提示:

  • x <= 50000
  • trackgetRankOfNumber 方法的调用次数均不超过 2000 次

解答:

class StreamRank {
    List<Integer> list;
    public StreamRank() {
        list = new ArrayList<>();
    }
    
    public void track(int x) {
        int L = 0, R = list.size()-1;
        while(L<=R){
            int mid = L+(R-L)/2;
            if(list.get(mid)<x){
                L = mid+1;
            }else{
                R = mid-1;
            }
        }
        list.add(L, x);
    }
    
    public int getRankOfNumber(int x) {
        int L = 0, R = list.size()-1;
        while(L<=R){
            int mid = L+(R-L)/2;
            if(list.get(mid)>x){
                R = mid-1;
            }else{
                L = mid+1;
            }
        }
        return R+1;
    }
}

/**
 * Your StreamRank object will be instantiated and called as such:
 * StreamRank obj = new StreamRank();
 * obj.track(x);
 * int param_2 = obj.getRankOfNumber(x);
 */

面试题 10.11. 峰与谷

在一个整数数组中,“峰”是大于或等于相邻整数的元素,相应地,“谷”是小于或等于相邻整数的元素。例如,在数组{5, 8, 4, 2, 3, 4, 6}中,{8, 6}是峰, {5, 2}是谷。现在给定一个整数数组,将该数组按峰与谷的交替顺序排序。

示例:

输入: [5, 3, 1, 2, 3]
输出: [5, 1, 3, 2, 3]

提示:

  • nums.length <= 10000

解答:

class Solution {
    public void wiggleSort(int[] nums) {
        Arrays.sort(nums);
        for(int i=0;i<nums.length-1;i+=2){
            int temp = nums[i];
            nums[i] = nums[i+1];
            nums[i+1] = temp;
        }
    }
}

面试题 16.01. 交换数字

编写一个函数,不用临时变量,直接交换numbers = [a, b]ab的值。

示例:

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

提示:

  • numbers.length == 2
  • -2147483647 <= numbers[i] <= 2147483647

解答:

class Solution {
    public int[] swapNumbers(int[] numbers) {
        numbers[0] ^= numbers[1];
        numbers[1] ^= numbers[0];
        numbers[0] ^= numbers[1];
        return numbers;
    }
}

面试题 16.02. 单词频率

设计一个方法,找出任意指定单词在一本书中的出现频率。

你的实现应该支持如下操作:

  • WordsFrequency(book)构造函数,参数为字符串数组构成的一本书
  • get(word)查询指定单词在书中出现的频率

示例:

WordsFrequency wordsFrequency = new WordsFrequency({"i", "have", "an", "apple", "he", "have", "a", "pen"});
wordsFrequency.get("you"); //返回0,"you"没有出现过
wordsFrequency.get("have"); //返回2,"have"出现2次
wordsFrequency.get("an"); //返回1
wordsFrequency.get("apple"); //返回1
wordsFrequency.get("pen"); //返回1

提示:

  • book[i]中只包含小写字母
  • 1 <= book.length <= 100000
  • 1 <= book[i].length <= 10
  • get函数的调用次数不会超过100000

解答:

class WordsFrequency {

    Map<String, Integer> m;
    public WordsFrequency(String[] book) {
        m = new HashMap<>();
        for(int i=0;i<book.length;i++){
            m.put(book[i], m.getOrDefault(book[i],0)+1);
        }
    }
    
    public int get(String word) {
        return m.getOrDefault(word, 0);
    }
}

/**
 * Your WordsFrequency object will be instantiated and called as such:
 * WordsFrequency obj = new WordsFrequency(book);
 * int param_1 = obj.get(word);
 */

面试题 16.03. 交点

给定两条线段(表示为起点start = {X1, Y1}和终点end = {X2, Y2}),如果它们有交点,请计算其交点,没有交点则返回空值。

要求浮点型误差不超过10^-6。若有多个交点(线段重叠)则返回 X 值最小的点,X 坐标相同则返回 Y 值最小的点。

示例 1:

输入:
line1 = {0, 0}, {1, 0}
line2 = {1, 1}, {0, -1}
输出: {0.5, 0}

示例 2:

输入:
line1 = {0, 0}, {3, 3}
line2 = {1, 1}, {2, 2}
输出: {1, 1}

示例 3:

输入:
line1 = {0, 0}, {1, 1}
line2 = {1, 0}, {2, 1}
输出: {},两条线段没有交点

提示:

  • 坐标绝对值不会超过 2^7
  • 输入的坐标均是有效的二维坐标

解答:

class Solution {
    double[] ans = new double[0];

    public double[] intersection(int[] start1, int[] end1, int[] start2, int[] end2) {
        int x1 = start1[0], y1 = start1[1];
        int x2 = end1[0], y2 = end1[1];
        int x3 = start2[0], y3 = start2[1];
        int x4 = end2[0], y4 = end2[1];

        // 判断 (x1, y1)~(x2, y2) 和 (x3, y3)~(x4, y4) 是否平行
        if ((y4 - y3) * (x2 - x1) == (y2 - y1) * (x4 - x3)) {
            // 若平行,则判断 (x3, y3) 是否在「直线」(x1, y1)~(x2, y2) 上
            if ((y2 - y1) * (x3 - x1) == (y3 - y1) * (x2 - x1)) {
                // 判断 (x3, y3) 是否在「线段」(x1, y1)~(x2, y2) 上
                if (inside(x1, y1, x2, y2, x3, y3)) {
                    update(x3, y3);
                }
                // 判断 (x4, y4) 是否在「线段」(x1, y1)~(x2, y2) 上
                if (inside(x1, y1, x2, y2, x4, y4)) {
                    update(x4, y4);
                }
                // 判断 (x1, y1) 是否在「线段」(x3, y3)~(x4, y4) 上
                if (inside(x3, y3, x4, y4, x1, y1)) {
                    update(x1, y1);
                }
                // 判断 (x2, y2) 是否在「线段」(x3, y3)~(x4, y4) 上
                if (inside(x3, y3, x4, y4, x2, y2)) {
                    update(x2, y2);
                }
            }
            // 在平行时,其余的所有情况都不会有交点
        } else {
            // 联立方程得到 t1 和 t2 的值
            double t1 = (double) (x3 * (y4 - y3) + y1 * (x4 - x3) - y3 * (x4 - x3) - x1 * (y4 - y3)) / ((x2 - x1) * (y4 - y3) - (x4 - x3) * (y2 - y1));
            double t2 = (double) (x1 * (y2 - y1) + y3 * (x2 - x1) - y1 * (x2 - x1) - x3 * (y2 - y1)) / ((x4 - x3) * (y2 - y1) - (x2 - x1) * (y4 - y3));
            // 判断 t1 和 t2 是否均在 [0, 1] 之间
            if (t1 >= 0.0 && t1 <= 1.0 && t2 >= 0.0 && t2 <= 1.0) {
                ans = new double[]{x1 + t1 * (x2 - x1), y1 + t1 * (y2 - y1)};
            }
        }
        return ans;
    }

    // 判断 (xk, yk) 是否在「线段」(x1, y1)~(x2, y2) 上
    // 这里的前提是 (xk, yk) 一定在「直线」(x1, y1)~(x2, y2) 上
    public boolean inside(int x1, int y1, int x2, int y2, int xk, int yk) {
        // 若与 x 轴平行,只需要判断 x 的部分
        // 若与 y 轴平行,只需要判断 y 的部分
        // 若为普通线段,则都要判断
        return (x1 == x2 || (Math.min(x1, x2) <= xk && xk <= Math.max(x1, x2))) && (y1 == y2 || (Math.min(y1, y2) <= yk && yk <= Math.max(y1, y2)));
    }

    public void update(double xk, double yk) {
        // 将一个交点与当前 ans 中的结果进行比较
        // 若更优则替换
        if (ans.length == 0 || xk < ans[0] || (xk == ans[0] && yk < ans[1])) {
            ans = new double[]{xk, yk};
        }
    }
}

面试题 16.04. 井字游戏

设计一个算法,判断玩家是否赢了井字游戏。输入是一个 N x N 的数组棋盘,由字符" ",“X"和"O"组成,其中字符” "代表一个空位。

以下是井字游戏的规则:

  • 玩家轮流将字符放入空位(" ")中。
  • 第一个玩家总是放字符"O",且第二个玩家总是放字符"X"。
  • "X"和"O"只允许放置在空位中,不允许对已放有字符的位置进行填充。
  • 当有N个相同(且非空)的字符填充任何行、列或对角线时,游戏结束,对应该字符的玩家获胜。
  • 当所有位置非空时,也算为游戏结束。
  • 如果游戏结束,玩家不允许再放置字符。

如果游戏存在获胜者,就返回该游戏的获胜者使用的字符(“X"或"O”);如果游戏以平局结束,则返回 “Draw”;如果仍会有行动(游戏未结束),则返回 “Pending”。

示例 1:

输入: board = ["O X"," XO","X O"]
输出: "X"

示例 2:

输入: board = ["OOX","XXO","OXO"]
输出: "Draw"
解释: 没有玩家获胜且不存在空位

示例 3:

输入: board = ["OOX","XXO","OX "]
输出: "Pending"
解释: 没有玩家获胜且仍存在空位

提示:

  • 1 <= board.length == board[i].length <= 100
  • 输入一定遵循井字棋规则

解答:

class Solution {
    public String tictactoe(String[] board) {
        int n = board.length;
        int sumX = (int)'X'*n, sumO = (int)'O'*n, flag=0;
        for(int i=0;i<n;i++){
            int a=0, b=0;
            for(int j=0;j<n;j++){
                a+=(int)board[i].charAt(j);
                b+=(int)board[j].charAt(i);
                if(board[i].charAt(j)==' ') flag=1;
            }
            if(a==sumX||b==sumX) return "X";
            if(a==sumO||b==sumO) return "O";
        }
        int a=0, b=0;
        for(int i=0;i<n;i++){
            a+=(int)board[i].charAt(i);
            b+=(int)board[i].charAt(n-1-i);
        }
        if(a==sumX||b==sumX) return "X";
        if(a==sumO||b==sumO) return "O";
        if (flag==1) return "Pending";
        return "Draw";
    }
}

面试题 16.05. 阶乘尾数

设计一个算法,算出 n 阶乘有多少个尾随零。

示例 1:

输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。

示例 2:

输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.

说明: 你算法的时间复杂度应为 O(log n) 。

解答:

class Solution {
    public int trailingZeroes(int n) {
        int ans = 0;
        while(n>=5){
            n /= 5;
            ans += n;
        }
        return ans;
    }
}

面试题 16.06. 最小差

给定两个整数数组ab,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该对数值的差

示例:

输入:{1, 3, 15, 11, 2}, {23, 127, 235, 19, 8}
输出:3,即数值对(11, 8)

提示:

  • 1 <= a.length, b.length <= 100000
  • -2147483648 <= a[i], b[i] <= 2147483647
  • 正确结果在区间 [0, 2147483647]

解答:

class Solution {
    public int smallestDifference(int[] a, int[] b) {
        Arrays.sort(a);
        Arrays.sort(b);
        int i=0, j=0;
        long min = Long.MAX_VALUE;
        while(i<a.length && j<b.length){
            if(a[i]==b[j]) return 0;
            else if(a[i]>b[j]){
                min = Math.min(min,(long)a[i]-(long)b[j]);
                j++;
            }else{
                min = Math.min(min,(long)b[j]-(long)a[i]);
                i++;
            }
        }
        return (int)min;
    }
}

面试题 16.07. 最大数值

编写一个方法,找出两个数字ab中最大的那一个。不得使用if-else或其他比较运算符。

示例:

输入: a = 1, b = 2
输出: 2

解答:

class Solution {
    public int maximum(int a, int b) {
        int k = (int) (((long) a - (long) b) >>> 63 & 1);
        return a * (1 - k) + b * k;
    }
}

面试题 16.08. 整数的英语表示

给定一个整数,打印该整数的英文描述。

示例 1:

输入: 123
输出: "One Hundred Twenty Three"

示例 2:

输入: 12345
输出: "Twelve Thousand Three Hundred Forty Five"

示例 3:

输入: 1234567
输出: "One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"

示例 4:

输入: 1234567891
输出: "One Billion Two Hundred Thirty Four Million Five Hundred Sixty Seven Thousand Eight Hundred Ninety One"

注意:本题与 273 题相同:https://leetcode-cn.com/problems/integer-to-english-words/

解答:

class Solution {
    String[] singles = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};
    String[] teens = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
    String[] tens = {"", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
    String[] thousands = {"", "Thousand", "Million", "Billion"};

    public String numberToWords(int num) {
        if (num == 0) {
            return "Zero";
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 3, unit = 1000000000; i >= 0; i--, unit /= 1000) {
            int curNum = num / unit;
            if (curNum != 0) {
                num -= curNum * unit;
                StringBuffer curr = new StringBuffer();
                recursion(curr, curNum);
                curr.append(thousands[i]).append(" ");
                sb.append(curr);
            }
        }
        return sb.toString().trim();
    }

    public void recursion(StringBuffer curr, int num) {
        if (num == 0) {
            return;
        } else if (num < 10) {
            curr.append(singles[num]).append(" ");
        } else if (num < 20) {
            curr.append(teens[num - 10]).append(" ");
        } else if (num < 100) {
            curr.append(tens[num / 10]).append(" ");
            recursion(curr, num % 10);
        } else {
            curr.append(singles[num / 100]).append(" Hundred ");
            recursion(curr, num % 100);
        }
    }
}

面试题 16.09. 运算

请实现整数数字的乘法、减法和除法运算,运算结果均为整数数字,程序中只允许使用加法运算符和逻辑运算符,允许程序中出现正负常数,不允许使用位运算。

你的实现应该支持如下操作:

  • Operations() 构造函数
  • minus(a, b) 减法,返回a - b
  • multiply(a, b) 乘法,返回a * b
  • divide(a, b) 除法,返回a / b

示例:

Operations operations = new Operations();
operations.minus(1, 2); //返回-1
operations.multiply(3, 4); //返回12
operations.divide(5, -2); //返回-2

提示:

  • 你可以假设函数输入一定是有效的,例如不会出现除法分母为0的情况
  • 单个用例的函数调用次数不会超过1000次

解答:

class Operations {

    // 用来获取-1
    int ne = Integer.MAX_VALUE + Integer.MAX_VALUE + 1;

    long[] neCache = new long[32];// 放置 -1,-2,-4,-8...
    long[] poCache = new long[32];// 放置 1,2,4,8...
    long[] cache = new long[32];// 存放乘数或除数的倍数,1*a,2*a,4*a,8*a...主要用于快速计算,不然容易超时
    long[] cache1 = new long[32];// 存放乘数或除数的倍数 负数-1*a,-2*a,-4*a,-8*a

    public Operations() {
        neCache[0] = ne;
        poCache[0] = 1;
        for (int i = 1; i < 32; ++i) {
            neCache[i] = neCache[i + ne] + neCache[i + ne];
            poCache[i] = poCache[i + ne] + poCache[i + ne];
        }
    }

    public int minus(int a, int b) {
        if (a == b) return 0;
        int index = 31;// 从最大值开始比较
        while (b != 0) {
            if (b > 0) {
                if (b >= poCache[index]) { // 如果b大于2的index次方,
                    b += neCache[index];// a与b同时减
                    a += neCache[index];
                } else {
                    index += ne;
                }
            } else { // b小于0时同理
                if (b <= neCache[index]) {
                    b += poCache[index];
                    a += poCache[index];
                } else {
                    index += ne;
                }
            }
        }
        return a;
    }

    public int multiply(int a, int b) {
        if (a == 0 || b == 0) return 0;
        if (a == 1) return b;
        if (b == 1) return a;
        if (a == ne) return minus(0, b);
        if (b == ne) return minus(0, a);
        int sign = (a > 0 && b > 0) || (a < 0 && b < 0) ? 1 : ne;
        // 把b变成正数
        if (b < 0) {
            b = minus(0, b);
        }

        cache[0] = a;
        for (int i = 1; i < 32; i++) {
            cache[i] = cache[i + ne] + cache[i + ne];
        }
        int index = 30; // 从31开始应该也是可以的
        int ret = 0;
        int retSign = a > 0 ? 1 : ne; // 记录返回值的符号
        while (b > 0) {
            if (b >= poCache[index]) {
                b += neCache[index];
                ret += cache[index];
                retSign = ret > 0 ? 1 : ne;// 记录返回值的符号
            } else {
                index += ne;
            }
        }
        // 根据初始值改变返回值的符号
        if ((sign < 0 && ret > 0) || (sign > 0 && ret < 0)) {
            ret = minus(0, ret);
        }
        // 结果溢出,返回值的符号会变成相反的
        if (retSign != (a > 0 ? 1 : ne)) {
            ret = minus(0, ret);
        }
        return ret;
    }

    public int divide(int a, int b) {
        if (a == 0) return 0;
        if (b == 1) return a;
        if (b == ne) return minus(0, a);
        int ret = 0;
        int sign = (a > 0 && b > 0) || (a < 0 && b < 0) ? 1 : ne;
        long nb = b;
        long pb = b;
        if (b < 0) {
            b = minus(0, b);
        } else {
            nb = minus(0, b);
        }
        if (a < 0) {
            a = minus(0, a);
        }
        cache[0] = b;
        cache1[0] = nb;
        int index = 1;
        for (; index < 32; ++index) {
            cache[index] = cache[index + ne] + cache[index + ne];
            cache1[index] = cache1[index + ne] + cache1[index + ne];
            if (cache1[index] >= a) {
                break; // 找到最大值就可以返回了,不用计算完
            }
        }
        if (index >= 32) index = 31;
        while (a >= b) {
            if (a >= cache[index]) {
                ret += poCache[index];// 注意这里是2的index次方的值
                a += cache1[index];
            } else {
                index += ne;
            }
        }
        if (sign < 0) {
            ret = minus(0, ret);
        }
        return ret;
    }
}

面试题 16.10. 生存人数

给定 N 个人的出生年份和死亡年份,第 i 个人的出生年份为 birth[i],死亡年份为 death[i],实现一个方法以计算生存人数最多的年份。

你可以假设所有人都出生于 1900 年至 2000 年(含 1900 和 2000 )之间。如果一个人在某一年的任意时期处于生存状态,那么他应该被纳入那一年的统计中。例如,生于 1908 年、死于 1909 年的人应当被列入 1908 年和 1909 年的计数。

如果有多个年份生存人数相同且均为最大值,输出其中最小的年份。

示例:

输入:
birth = {1900, 1901, 1950}
death = {1948, 1951, 2000}
输出: 1901

提示:

  • 0 < birth.length == death.length <= 10000
  • birth[i] <= death[i]

解答:

class Solution {
    public int maxAliveYear(int[] birth, int[] death) {
        int[] change = new int[102];
        for(int i=0;i<birth.length;i++){
            change[birth[i]-1900]++;
            change[death[i]-1899]--;;
        }
        int sum = 0, max = 0, ans = 1900;
        for(int i=0;i<101;i++){
            sum += change[i];
            if(sum > max){
                max = sum;
                ans = 1900 + i;
            }
        }
        return ans;
    }
}

面试题 16.11. 跳水板

你正在使用一堆木板建造跳水板。有两种类型的木板,其中长度较短的木板长度为shorter,长度较长的木板长度为longer。你必须正好使用k块木板。编写一个方法,生成跳水板所有可能的长度。

返回的长度需要从小到大排列。

示例 1

输入:
shorter = 1
longer = 2
k = 3
输出: [3,4,5,6]
解释:
可以使用 3 次 shorter,得到结果 3;使用 2 次 shorter 和 1 次 longer,得到结果 4 。以此类推,得到最终结果。

提示:

  • 0 < shorter <= longer
  • 0 <= k <= 100000

解答:

class Solution {
    List<Integer> list = new LinkedList<>();
    public int[] divingBoard(int shorter, int longer, int k) {
        if(k==0){
            return new int[0];
        }
        if(shorter==longer){
            return new int[]{shorter*k};
        }
        int[] ans = new int[k+1];
        for(int i=0;i<=k;i++){
            ans[i] = shorter*(k-i)+longer*i;
        }
        return ans;
    }
}

面试题 16.13. 平分正方形

给定两个正方形及一个二维平面。请找出将这两个正方形分割成两半的一条直线。假设正方形顶边和底边与 x 轴平行。

每个正方形的数据square包含3个数值,正方形的左下顶点坐标[X,Y] = [square[0],square[1]],以及正方形的边长square[2]。所求直线穿过两个正方形会形成4个交点,请返回4个交点形成线段的两端点坐标(两个端点即为4个交点中距离最远的2个点,这2个点所连成的线段一定会穿过另外2个交点)。2个端点坐标[X1,Y1][X2,Y2]的返回格式为{X1,Y1,X2,Y2},要求若X1 != X2,需保证X1 < X2,否则需保证Y1 <= Y2

若同时有多条直线满足要求,则选择斜率最大的一条计算并返回(与Y轴平行的直线视为斜率无穷大)。

示例:

输入:
square1 = {-1, -1, 2}
square2 = {0, -1, 2}
输出: {-1,0,2,0}
解释: 直线 y = 0 能将两个正方形同时分为等面积的两部分,返回的两线段端点为[-1,0]和[2,0]

提示:

  • square.length == 3
  • square[2] > 0

解答:

class Solution {
    public double[] cutSquares(int[] square1, int[] square2) {
        //第一个正方形的中心点,x,y坐标及正方形边长
        double x1 = square1[0] + square1[2]/2.0;
        double y1 = square1[1] + square1[2]/2.0;
        int d1 = square1[2];
        //第二个正方形的中心点,x,y坐标及正方形边长
        double x2 = square2[0] + square2[2]/2.0;
        double y2 = square2[1] + square2[2]/2.0;
        int d2 = square2[2];
        //结果集
        double[] res = new double[4];
        //两个中心坐标在同一条x轴上,此时两条直线的斜率都是无穷大
        if(x1 == x2){
            res[0] = x1;
            res[1] = Math.min(square1[1], square2[1]);
            res[2] = x1;
            res[3] = Math.max(square1[1] + d1, square2[1] + d2);
        }else{
            //斜率存在,则计算斜率和系数,y = kx + b;
            double k = (y1 - y2)/(x1 - x2);//斜率计算公式
            double b = y1 - k*x1;
            //斜率绝对值大于1,说明与正方形的上边和下边相交
            if(Math.abs(k) > 1){
            //先计算底边,也就是两个正方形左下坐标y的最小值
                res[1] = Math.min(square1[1],square2[1]);
                res[0] = (res[1] - b)/k;
            //再计算顶边,也就是两个正方形左下坐标y+边长的最大值
                res[3] = Math.max(square1[1] + d1,square2[1] + d2);
                res[2] = (res[3] - b)/k;
            }else{
                //斜率绝对值小于等于1,说明与正方形的左边和右边相交,同理
                res[0] = Math.min(square1[0],square2[0]);
                res[1] = res[0]*k + b;
                res[2] = Math.max(square1[0] + d1,square2[0] + d2);
                res[3] = res[2]*k + b;
            }
        }
        //题目要求x1 < x2,如果结果不满足,我们交换两个点的坐标即可
        if(res[0] > res[2]){
            swap(res, 0 ,2);
            swap(res, 1, 3);
        }
        return res;
    }
    public void swap(double[] res, int x, int y){
        double temp = res[x];
        res[x] = res[y];
        res[y] = temp;
    }
}

面试题 16.14. 最佳直线

给定一个二维平面及平面上的 N 个点列表Points,其中第i个点的坐标为Points[i]=[Xi,Yi]。请找出一条直线,其通过的点的数目最多。

设穿过最多点的直线所穿过的全部点编号从小到大排序的列表为S,你仅需返回[S[0],S[1]]作为答案,若有多条直线穿过了相同数量的点,则选择S[0]值较小的直线返回,S[0]相同则选择S[1]值较小的直线返回。

示例:

输入: [[0,0],[1,1],[1,0],[2,0]]
输出: [0,2]
解释: 所求直线穿过的3个点的编号为[0,2,3]

提示:

  • 2 <= len(Points) <= 300
  • len(Points[i]) = 2

解答:

class Solution {
    public int[] bestLine(int[][] points) {
        int countMax = 0;ArrayList<Integer> arr=new ArrayList<Integer>();
        for(int i=0;i<points.length;i++){
            int max=points.length-i-1,x0=points[i][0],y0=points[i][1];double []listO = new double[max];
            for(int j=i+1;j<points.length;j++){
                int x1=points[j][0],y1=points[j][1];
                listO[j-i-1] = x1==x0? Double.MAX_VALUE:1.0*(y1-y0)/(x1-x0);
            }
            double []list = new double[max];for(int j=0;j<max;j++) list[j] = listO[j];Arrays.sort(list);
            double cur=list[0],curMax=0;int curCount=1;boolean flag=false;
            for(int j=0;j<list.length;j++){
                if(Math.abs(list[j]-cur)<0.000001)curCount++;
                else{            
                    if(curCount>countMax){flag=true;countMax=curCount;curMax=cur;}
                    cur=list[j];curCount=2;
                }
                if(j==list.length-1 && curCount>countMax){flag=true;countMax=curCount;curMax=cur;}
            }
            if(flag){
                arr.clear();arr.add(i);for(int j=i+1;j<points.length;j++)if(Math.abs(listO[j-i-1]-curMax)<0.000001)arr.add(j);
            }
            if(arr.size()>=max-1)return new int[]{arr.get(0),arr.get(1)};
        }
        return new int[]{arr.get(0),arr.get(1)};
    }
}

面试题 16.15. 珠玑妙算

珠玑妙算游戏(the game of master mind)的玩法如下。

计算机有4个槽,每个槽放一个球,颜色可能是红色(R)、黄色(Y)、绿色(G)或蓝色(B)。例如,计算机可能有RGGB 4种(槽1为红色,槽2、3为绿色,槽4为蓝色)。作为用户,你试图猜出颜色组合。打个比方,你可能会猜YRGB。要是猜对某个槽的颜色,则算一次“猜中”;要是只猜对颜色但槽位猜错了,则算一次“伪猜中”。注意,“猜中”不能算入“伪猜中”。

给定一种颜色组合solution和一个猜测guess,编写一个方法,返回猜中和伪猜中的次数answer,其中answer[0]为猜中的次数,answer[1]为伪猜中的次数。

示例:

输入: solution="RGBY",guess="GGRR"
输出: [1,1]
解释: 猜中1次,伪猜中1次。

提示:

  • len(solution) = len(guess) = 4
  • solutionguess仅包含"R","G","B","Y"这4种字符

解答1:

class Solution {
    public int[] masterMind(String solution, String guess) {
        int ansA=0, ansB=0;
        int[] m = new int[4];
        for(int i=0;i<4;i++){
            char a = solution.charAt(i);
            char b = guess.charAt(i);
            if(a==b){
                ansA++;
            }else{
                if(b=='R'){
                    m[0]++;
                }else if(b=='Y'){
                    m[1]++;
                }else if(b=='G'){
                    m[2]++;
                }else{
                    m[3]++;
                }
            }
        }
        for(int i=0;i<4;i++){
            char a = solution.charAt(i);
            char b = guess.charAt(i);
            if(a!=b){
                if(a=='R'){
                    if(m[0]>0){
                        m[0]--;
                        ansB++;
                    }
                }else if(a=='Y'){
                    if(m[1]>0){
                        m[1]--;
                        ansB++;
                    }
                }else if(a=='G'){
                    if(m[2]>0){
                        m[2]--;
                        ansB++;
                    }
                }else{
                    if(m[3]>0){
                        m[3]--;
                        ansB++;
                    }
                }
            }
        }
        return new int[]{ansA,ansB};
    }
}

解答2:

class Solution {
    public int[] masterMind(String solution, String guess) {
        int[] ans = new int[2];
        int[] m1 = new int[4];
        int[] m2 = new int[4];
        for(int i=0;i<4;i++){
            char a = solution.charAt(i);
            char b = guess.charAt(i);
            if(a==b){
                ans[0]++;
            }else{
                fun(m1, a);
                fun(m2, b);
            }
        }
        for(int i=0;i<4;i++){
            ans[1] += Math.min(m1[i],m2[i]);
        }
        return ans;
    }
    public void fun(int[] m, char c){
        if(c=='R'){
            m[0]++;
        }else if(c=='Y'){
            m[1]++;
        }else if(c=='G'){
            m[2]++;
        }else{
            m[3]++;
        }
    }
}

面试题 16.16. 部分排序

给定一个整数数组,编写一个函数,找出索引mn,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的mn(例如整个数组是有序的),请返回[-1,-1]

示例:

输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
输出: [3,9]

提示:

  • 0 <= len(array) <= 1000000

解答:

class Solution {
    public int[] subSort(int[] array) {
        int[] a = array.clone();
        int[] ans = new int[]{-1,-1};
        int n = array.length;
        Arrays.sort(array);
        for(int i=0;i<n;i++){
            if(array[i]!=a[i]){
                ans[0] = i;
                break;
            }
        }
        if(ans[0]==-1) return ans;
        for(int i=n-1;i>=0;i--){
            if(array[i]!=a[i]){
                ans[1] = i;
                break;
            }
        }
        return ans;
    }
}

面试题 16.17. 连续数列

给定一个整数数组,找出总和最大的连续数列,并返回总和。

示例:

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

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

解答1:

class Solution {
    public int maxSubArray(int[] nums) {
        int ans = nums[0], n = nums.length;
        for(int i=0;i<n;i++){
            int temp = 0;
            int j = i;
            while(j<n&&temp>=0){
                temp += nums[j];
                ans = Math.max(ans, temp);
                j++;
            }
        }
        return ans;
    }
}

解答2:

class Solution {
    public int maxSubArray(int[] nums) {
        int ans = nums[0], temp = 0;
        for(int x : nums){
            temp = Math.max(temp+x, x);
            ans = Math.max(ans, temp);
        }
        return ans;
    }
}

面试题 16.18. 模式匹配

你有两个字符串,即patternvaluepattern字符串由字母"a""b"组成,用于描述字符串中的模式。例如,字符串"catcatgocatgo"匹配模式"aabab"(其中"cat""a""go""b"),该字符串也匹配像"a""ab""b"这样的模式。但需注意"a""b"不能同时表示相同的字符串。编写一个方法判断value字符串是否匹配pattern字符串。

示例 1:

输入: pattern = "abba", value = "dogcatcatdog"
输出: true

示例 2:

输入: pattern = "abba", value = "dogcatcatfish"
输出: false

示例 3:

输入: pattern = "aaaa", value = "dogcatcatdog"
输出: false

示例 4:

输入: pattern = "abba", value = "dogdogdogdog"
输出: true
解释: "a"="dogdog",b="",反之也符合规则

提示:

  • 1 <= len(pattern) <= 1000
  • 0 <= len(value) <= 1000
  • 你可以假设pattern只包含字母"a""b"value仅包含小写字母。

解答:

class Solution {
    public boolean patternMatching(String pattern, String value) {
        int count_a = 0, count_b = 0;
        for (char ch: pattern.toCharArray()) {
            if (ch == 'a') {
                ++count_a;
            } else {
                ++count_b;
            }
        }
        if (count_a < count_b) {
            int temp = count_a;
            count_a = count_b;
            count_b = temp;
            char[] array = pattern.toCharArray();
            for (int i = 0; i < array.length; i++) {
                array[i] = array[i] == 'a' ? 'b' : 'a';
            }
            pattern = new String(array);
        }
        if (value.length() == 0) {
            return count_b == 0;
        }
        if (pattern.length() == 0) {
            return false;
        }
        for (int len_a = 0; count_a * len_a <= value.length(); ++len_a) {
            int rest = value.length() - count_a * len_a;
            if ((count_b == 0 && rest == 0) || (count_b != 0 && rest % count_b == 0)) {
                int len_b = (count_b == 0 ? 0 : rest / count_b);
                int pos = 0;
                boolean correct = true;
                String value_a = "", value_b = "";
                for (char ch: pattern.toCharArray()) {
                    if (ch == 'a') {
                        String sub = value.substring(pos, pos + len_a);
                        if (value_a.length() == 0) {
                            value_a = sub;
                        } else if (!value_a.equals(sub)) {
                            correct = false;
                            break;
                        }
                        pos += len_a;
                    } else {
                        String sub = value.substring(pos, pos + len_b);
                        if (value_b.length() == 0) {
                            value_b = sub;
                        } else if (!value_b.equals(sub)) {
                            correct = false;
                            break;
                        }
                        pos += len_b;
                    }
                }
                if (correct && !value_a.equals(value_b)) {
                    return true;
                }
            }
        }
        return false;
    }
}

面试题 16.19. 水域大小

你有一个用于表示一片土地的整数矩阵land,该矩阵中每个点的值代表对应地点的海拔高度。若值为0则表示水域。由垂直、水平或对角连接的水域为池塘。池塘的大小是指相连接的水域的个数。编写一个方法来计算矩阵中所有池塘的大小,返回值需要从小到大排序。

示例:

输入:
[
  [0,2,1,0],
  [0,1,0,1],
  [1,1,0,1],
  [0,1,0,1]
]
输出: [1,2,4]

提示:

  • 0 < len(land) <= 1000
  • 0 < len(land[i]) <= 1000

解答:

class Solution {
    int[][] moves = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
    public int[] pondSizes(int[][] land) {
        ArrayList<Integer> result = new ArrayList<>();
        int row = land.length,col = land[0].length;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (land[i][j] == 0) {
                    land[i][j] = -1;
                    int res = 0;
                    LinkedList<int[]> queue = new LinkedList<>();
                    queue.add(new int[]{i, j});
                    while (!queue.isEmpty()) {
                        int[] poll = queue.poll();
                        res++;
                        for (int[] move : moves) {
                            int x = poll[0] + move[0];
                            int y = poll[1] + move[1];
                            if (x < 0 || x >= row || y < 0 || y >= col || land[x][y] != 0) {
                                continue;
                            }
                            queue.add(new int[]{x, y});
                            land[x][y] = -1;
                        }
                    }
                    result.add(res);
                }
            }
        }
        Collections.sort(result);
        int[] ints = new int[result.size()];
        for (int i = 0; i < result.size(); i++) {
            ints[i] = result.get(i);
        }
        return ints;
    }
}

面试题 16.20. T9键盘

在老式手机上,用户通过数字键盘输入,手机将提供与这些数字相匹配的单词列表。每个数字映射到0至4个字母。给定一个数字序列,实现一个算法来返回匹配单词的列表。你会得到一张含有有效单词的列表。映射如下图所示:

img

示例 1:

输入: num = "8733", words = ["tree", "used"]
输出: ["tree", "used"]

示例 2:

输入: num = "2", words = ["a", "b", "c", "d"]
输出: ["a", "b", "c"]

提示:

  • num.length <= 1000
  • words.length <= 500
  • words[i].length == num.length
  • num中不会出现 0, 1 这两个数字

解答:

class Solution {
    public List<String> getValidT9Words(String num, String[] words) {
        Map<Integer,String> map = new HashMap<>();
        map.put(2,"abc");
        map.put(3,"def");
        map.put(4,"ghi");
        map.put(5,"jkl");
        map.put(6,"mno");
        map.put(7,"pqrs");
        map.put(8,"tuv");
        map.put(9,"wxyz");
        int len = num.length();
        Trie t = new Trie(map);
        t.insert(num);
        List<String> list = new ArrayList<>();
        for(String s : words){
            if(s.length() == len && t.starts(s)) list.add(s);
        }
        return list;
    }
}
class Trie {
    private Trie[] children;
    Map<Integer,String> map;
    public Trie(Map<Integer,String> map){
        this.map = map;
        children = new Trie[26];
    }
    public Trie(){
        children = new Trie[26];
    }

    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            char ch = word.charAt(i);
            int index = ch - '0';
            String s = map.get(index);
            Trie t = new Trie();
            for(char c : s.toCharArray()){
                if (node.children[c-'a'] == null) {
                    node.children[c-'a'] = t;
                }
            }
            node = t;
        }
    }

    public boolean starts(String prefix) {
        return searchPrefix(prefix) != null;
    }

    private Trie searchPrefix(String prefix) {
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            char ch = prefix.charAt(i);
            int index = ch - 'a';
            if (node.children[index] == null) {
                return null;
            }
            node = node.children[index];
        }
        return node;
    }
}

面试题 16.21. 交换和

给定两个整数数组,请交换一对数值(每个数组中取一个数值),使得两个数组所有元素的和相等。

返回一个数组,第一个元素是第一个数组中要交换的元素,第二个元素是第二个数组中要交换的元素。若有多个答案,返回任意一个均可。若无满足条件的数值,返回空数组。

示例:

输入: array1 = [4, 1, 2, 1, 1, 2], array2 = [3, 6, 3, 3]
输出: [1, 3]

示例:

输入: array1 = [1, 2, 3], array2 = [4, 5, 6]
输出: []

提示:

  • 1 <= array1.length, array2.length <= 100000

解答:

class Solution {
    public int[] findSwapValues(int[] array1, int[] array2) {
        int sum1 = 0, sum2 = 0;
        Set<Integer> container = new HashSet<>();
        for (int num : array1) sum1 += num;
        for (int num : array2) {
            container.add(num);
            sum2 += num;
        }
        int diff = sum1 - sum2;
        if (diff % 2 != 0) return new int[]{};
        diff /= 2;
        for (int num : array1){
            if (container.contains(num - diff)) return new int[]{num, num - diff};
        }
        return new int[]{};
    }
}

面试题 16.22. 兰顿蚂蚁

一只蚂蚁坐在由白色和黑色方格构成的无限网格上。开始时,网格全白,蚂蚁面向右侧。每行走一步,蚂蚁执行以下操作。

(1) 如果在白色方格上,则翻转方格的颜色,向右(顺时针)转 90 度,并向前移动一个单位。
(2) 如果在黑色方格上,则翻转方格的颜色,向左(逆时针方向)转 90 度,并向前移动一个单位。

编写程序来模拟蚂蚁执行的前 K 个动作,并返回最终的网格。

网格由数组表示,每个元素是一个字符串,代表网格中的一行,黑色方格由 'X' 表示,白色方格由 '_' 表示,蚂蚁所在的位置由 'L', 'U', 'R', 'D' 表示,分别表示蚂蚁 左、上、右、下 的朝向。只需要返回能够包含蚂蚁走过的所有方格的最小矩形。

示例 1:

输入: 0
输出: ["R"]

示例 2:

输入: 2
输出:
[
  "_X",
  "LX"
]

示例 3:

输入: 5
输出:
[
  "_U",
  "X_",
  "XX"
]

说明:

  • K <= 100000

解答:

class Solution {
    private class Position {
        
        // 横坐标 x 纵坐标 y
        int x, y;
        
        public Position(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) return true;
            if (!(obj instanceof Position)) return false;
            Position o = (Position) obj;
            return x == o.x && y == o.y;
        }
        
        // 改写哈希算法,使两个 Position 对象可以比较坐标而不是内存地址
        @Override
        public int hashCode() {
            int result = x;
            result = 31 * result + y;
            return result;
        }
    }
    
    public List<String> printKMoves(int K) {
        char[] direction = {'L', 'U', 'R', 'D'};
        // 用“向量”记录方向,顺序与上一行方向的字符顺序保持一致,每个元素的后一个元素都是可以90°向右变换得到的
        int[][] offset = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
        // 蚂蚁的位置
        Position antPos = new Position(0, 0);
        // 蚂蚁方向的向量序号
        int antDir = 2;
        // 用集合存储所有黑块的坐标,一开始想再定义一个路径的坐标集合,发现可以直接用黑块+蚂蚁位置也能过
        Set<Position> blackSet = new HashSet<>();
        while (K > 0) {
            // 新的坐标对象用于放入集合
            Position t = new Position(antPos.x, antPos.y);
            // 如果黑块集合能存入,说明脚下的块不在集合中,也就意味着是白色,方向序号循环自增1
            if (blackSet.add(t)) antDir = (antDir + 1) % 4;
            else {
                // 否则说明脚下的块已经在集合中,也就意味着是黑色,方向序号循环自增3,相当于自减1,但是Math.floorMod取模可能消耗大?用+3替代
                antDir = (antDir + 3) % 4;
                // 别忘了删除,即将黑块变白
                blackSet.remove(t);
            }
            // 蚂蚁移动位置
            antPos.x += offset[antDir][0];
            antPos.y += offset[antDir][1];
            K--;
        }
        // 计算边界,即输出网格的行数和列数
        int left = antPos.x, top = antPos.y, right = antPos.x, bottom = antPos.y;
        for (Position pos : blackSet) {
            left = pos.x < left ? pos.x : left;
            top = pos.y < top ? pos.y : top;
            right = pos.x > right ? pos.x : right;
            bottom = pos.y > bottom ? pos.y : bottom;
        }
        char[][] grid = new char[bottom - top + 1][right - left + 1];
        // 填充白块
        for (char[] row : grid)
            Arrays.fill(row, '_');
        // 替换黑块
        for (Position pos : blackSet)
            grid[pos.y - top][pos.x - left] = 'X';
        // 替换蚂蚁
        grid[antPos.y - top][antPos.x - left] = direction[antDir];
        // 利用网格生成字符串列表
        List<String> result = new ArrayList<>();
        for (char[] row : grid)
            result.add(String.valueOf(row));
        return result;
    }
}

面试题 16.24. 数对和

设计一个算法,找出数组中两数之和为指定值的所有整数对。一个数只能属于一个数对。

示例 1:

输入: nums = [5,6,5], target = 11
输出: [[5,6]]

示例 2:

输入: nums = [5,6,5,6], target = 11
输出: [[5,6],[5,6]]

提示:

  • nums.length <= 100000

解答:

class Solution {
    public List<List<Integer>> pairSums(int[] nums, int target) {
        Arrays.sort(nums);
        int L = 0, R = nums.length-1;
        List<List<Integer>> ans = new LinkedList<>();
        while(L<R){
            int sum = nums[L]+nums[R];
            if(sum==target){
                ans.add(Arrays.asList(nums[L], nums[R]));
                L++;
                R--;
            }else if(sum<target){
                L++;
            }else{
                R--;
            }
        }
        return ans;
    }
}

面试题 16.25. LRU 缓存

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。

它应该支持以下操作: 获取数据 get 和 写入数据 put

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

解答:

public class LRUCache {
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode() {}
        public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
    }

    private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
    private int size;
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            // 如果 key 不存在,创建一个新的节点
            DLinkedNode newNode = new DLinkedNode(key, value);
            // 添加进哈希表
            cache.put(key, newNode);
            // 添加至双向链表的头部
            addToHead(newNode);
            ++size;
            if (size > capacity) {
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode tail = removeTail();
                // 删除哈希表中对应的项
                cache.remove(tail.key);
                --size;
            }
        }
        else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node.value = value;
            moveToHead(node);
        }
    }

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

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

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

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

面试题 16.26. 计算器

给定一个包含正整数、加(+)、减(-)、乘(*)、除(/)的算数表达式(括号除外),计算其结果。

表达式仅包含非负整数,+-*/ 四种运算符和空格 。 整数除法仅保留整数部分。

示例 1:

输入: "3+2*2"
输出: 7

示例 2:

输入: " 3/2 "
输出: 1

示例 3:

输入: " 3+5 / 2 "
输出: 5

说明:

  • 你可以假设所给定的表达式都是有效的。
  • 不要使用内置的库函数 eval

解答:

class Solution {
    public int calculate(String s) {
        char[] cs = s.trim().toCharArray();
        Stack<Integer> stack =new Stack();
        int ans = 0, i = 0;
        while(i<cs.length){
            if(cs[i]==' '){
                i++;
                continue;
            }
            char c = cs[i];
            if(c=='*'||c=='/'||c=='+'||c=='-'){
                i++;
                while(i<cs.length&&cs[i]==' ') i++;
            }
            int num = 0;
            while(i<cs.length&&Character.isDigit(cs[i])){
                num = num*10+cs[i]-'0';
                i++;
            }
            if(c=='-'){
                num = -num;
            }else if(c=='*'){
                num = stack.pop()*num;
            }else if(c=='/'){
                num = stack.pop()/num;
            }
            stack.push(num);
        }
        while(!stack.isEmpty()) ans += stack.pop();
        return ans;
    }
}

面试题 17.01. 不用加号的加法

设计一个函数把两个数字相加。不得使用 + 或者其他算术运算符。

示例:

输入: a = 1, b = 1
输出: 2

提示:

  • a, b 均可能是负数或 0
  • 结果不会溢出 32 位整数

解答:

class Solution {
    public int add(int a, int b) {
        if(b==0) return a;
        return add(a^b, (a&b)<<1);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值