【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. 判定是否互为字符重排
给定两个字符串 s1
和 s2
,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。
示例 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",比原字符串长度更长。
提示:
- 字符串长度在[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. 字符串轮转
字符串轮转。给定两个字符串s1
和s2
,请编写代码检查s2
是否为s1
旋转而成(比如,waterbottle
是erbottlewat
旋转后的字符串)。
示例1:
输入:s1 = "waterbottle", s2 = "erbottlewat"
输出:True
示例2:
输入:s1 = "aa", s2 = "aba"
输出:False
提示:
- 字符串长度在[0, 100000]范围内。
说明:
- 你能只调用一次检查子串的方法吗?
解答:
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]
提示:
- 链表长度在[0, 20000]范围内。
- 链表元素在[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:
输入: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. 链表相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交**:**
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入: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:
输入: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:
输入: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
- 如果
listA
和listB
没有交点,intersectVal
为0
- 如果
listA
和listB
有交点,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:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
进阶:
- 你是否可以不用额外空间解决此题?
解答:
/**
* 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操作。
当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop
,popAt
应返回 -1.
示例1:
输入:
["StackOfPlates", "push", "push", "popAt", "pop", "pop"]
[[1], [1], [2], [1], [], []]
输出:
[null, null, null, 2, 1, -1]
示例2:
输入:
["StackOfPlates", "push", "push", "push", "popAt", "popAt", "popAt"]
[[2], [1], [2], [3], [0], [0], [0]]
输出:
[null, null, null, null, 2, 1, 3]
解答:
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
,size
和is 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. 栈排序
栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push
、pop
、peek
和 isEmpty
。当栈为空时,peek
返回 -1。
示例1:
输入:
["SortedStack", "push", "push", "peek", "pop", "peek"]
[[], [1], [2], [], [], []]
输出:
[null,null,null,1,null,2]
示例2:
输入:
["SortedStack", "pop", "pop", "push", "pop", "isEmpty"]
[[], [], [], [1], [], []]
输出:
[null,null,null,null,null,true]
说明:
- 栈中的元素数目在[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. 动物收容所
动物收容所。有家动物收容所只收容狗与猫,且严格遵守“先进先出”的原则。在收养该收容所的动物时,收养人只能收养所有动物中“最老”(由其进入收容所的时间长短而定)的动物,或者可以挑选猫或狗(同时必须收养此类动物中“最老”的)。换言之,收养人不能自由挑选想收养的对象。请创建适用于这个系统的数据结构,实现各种操作方法,比如enqueue
、dequeueAny
、dequeueDog
和dequeueCat
。允许使用Java内置的LinkedList数据结构。
enqueue
方法有一个animal
参数,animal[0]
代表动物编号,animal[1]
代表动物种类,其中 0 代表猫,1 代表狗。
dequeue*
方法返回一个列表[动物编号, 动物种类]
,若没有可以收养的动物,则返回[-1,-1]
。
示例1:
输入:
["AnimalShelf", "enqueue", "enqueue", "dequeueCat", "dequeueDog", "dequeueAny"]
[[], [[0, 0]], [[1, 0]], [], [], []]
输出:
[null,null,null,[0,0],[-1,-1],[1,0]]
示例2:
输入:
["AnimalShelf", "enqueue", "enqueue", "enqueue", "dequeueDog", "dequeueCat", "dequeueAny"]
[[], [[0, 0]], [[1, 0]], [[2, 1]], [], [], []]
输出:
[null,null,null,null,[2,1],[0,0],[1,0]]
说明:
- 收纳所的最大容量为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
提示:
- 节点数量n在[0, 1e5]范围内。
- 节点编号大于等于 0 小于 n。
- 图中可能存在自环和平行边。
解答:
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
提示:
- 树的节点数目范围为[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. 插入
给定两个整型数字 N
与 M
,以及表示比特位置的 i
与 j
(i <= j
,且从 0 位开始计算)。
编写一种方法,使 M
对应的二进制数字插入 N
对应的二进制数字的第 i ~ j
位区域,不足之处用 0
补齐。具体插入过程如图所示。
题目保证从 i
位到 j
位足以容纳 M
, 例如: M = 10011
,则 i~j
区域至少可容纳 5 位。
示例1:
输入:N = 1024(10000000000), M = 19(10011), i = 2, j = 6
输出:N = 1100(10001001100)
示例2:
输入: N = 0, M = 31(11111), i = 0, j = 4
输出:N = 31(11111)
解答:
class Solution {
public int insertBits(int N, int M, int i, int j) {
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无法被二进制准确表示
提示:
- 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]
提示:
num
的范围在[1, 2147483647]之间;- 如果找不到前一个或者后一个满足条件的正数,那么输出 -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
提示:
- 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
提示:
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
提示:
- 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 列。机器人只能向下或向右移动,但不能走到一些被禁止的网格(有障碍物)。设计一种算法,寻找机器人从左上角移动到右下角的路径。
网格中的障碍物和空位置分别用 1
和 0
来表示。
返回一条可行的路径,路径由经过的网格的行号和列号组成。左上角为 0 行 0 列。如果没有可行的路径,返回空数组。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: [[0,0],[0,1],[0,2],[1,2],[2,2]]
解释:
输入中标粗的位置即为输出表示的路径,即
0行0列(左上角) -> 0行1列 -> 0行2列 -> 1行2列 -> 2行2列(右下角)
**说明:**r 和 c 的值均不超过 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
说明:
- nums长度在[1, 1000000]之间
- 此题为原书中的 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:
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]
提示:
- 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, 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, 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 ,因为它不属于初始坐标点的周围区域。
提示:
image
和image[0]
的长度均在范围 [1, 50] 内。- 初始坐标点
(sr,sc)
满足0 <= sr < image.length
和0 <= 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
提示:
- 箱子的数目不大于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 的元素数量分别为 m 和 n。
示例:
输入:
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 (没有找到)
提示:
- 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
提示:
- 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
track
和getRankOfNumber
方法的调用次数均不超过 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]
中a
与b
的值。
示例:
输入: 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. 最小差
给定两个整数数组a
和b
,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该对数值的差
示例:
输入:{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. 最大数值
编写一个方法,找出两个数字a
和b
中最大的那一个。不得使用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
solution
和guess
仅包含"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. 部分排序
给定一个整数数组,编写一个函数,找出索引m
和n
,只要将索引区间[m,n]
的元素排好序,整个数组就是有序的。注意:n-m
尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n]
,若不存在这样的m
和n
(例如整个数组是有序的),请返回[-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. 模式匹配
你有两个字符串,即pattern
和value
。 pattern
字符串由字母"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个字母。给定一个数字序列,实现一个算法来返回匹配单词的列表。你会得到一张含有有效单词的列表。映射如下图所示:
示例 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);
}
}