0%

Problem:

给定一个二维数组,其每一行从左到右递增排序,从上到下也是递增排序。给定一个数,判断这个数是否在该二维数组中。

Consider the following 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]
]

Given target = 5, return true.
Given target = 20, return false.
Copy to clipboardErrorCopied
要求时间复杂度 O(M + N),空间复杂度 O(1)。其中 M 为行数,N 为 列数。

Intuition:

该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 public class CodingInterview_002 {
public static void main(String[] args) {
int[][] arr=new int[][]{ {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}};
System.out.println(Find(16,arr));
}
public static boolean Find(int target, int [][] matrix) {
if(matrix==null||matrix.length==0||matrix[0].length==0){
return false;
}
//从右上角出发,想到这个是本题的重点。一般习惯性都会考虑从左上角或者右下角出发开始写循环
// 本题从右上角出发的原因是每一行从左到右递增排序,从上到下也是递增排序。这就意味着只要向左走数字就会减少,向下数字就会变大。
int row=0;
int col=matrix[0].length-1;
while(row<=matrix.length-1&&col>=0){
if(target==matrix[row][col]){
return true;
}else if(target>matrix[row][col]){
row++;
}else{
col--;
}
}
return false;
}
}

Problem:

在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。
要求时间复杂度 O(N),空间复杂度 O(1)

Input:
{2, 3, 1, 0, 2, 5}

Output:
2

Intuition:

对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。
以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复:

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CodingInterview_001 {
public static void main(String[] args) {
int[] a=new int[]{2,3,4,0,3,5};
int ans=findRepeatNum(a);
if(ans==-1){
System.out.println("There isn't any repeat number.");
}else{
System.out.println(ans);
}
}

public static int findRepeatNum(int[] duplicate){
if(duplicate==null||duplicate.length<0){
return -1;
}
//我们在循环中保证看到一个元素i,就把它放到数组的第i个格子里。那么如果数组中有重复元素就等价于我
//在对某个元素j换位置的时候发现元素j所在的格子装的数也是j(因为题目已经限定好了数字是从0-n-1)
for(int i=0;i<duplicate.length;i++){
//如果duplicate[i]里装的就是i那么我们就什么都不做,直接把下标往后移
if(duplicate[i]!=i&&duplicate[duplicate[i]]==duplicate[i]){
return duplicate[i];
}else if(duplicate[i]!=i){
swap(duplicate,i,duplicate[i]);
}

}
return -1;
}

private static void swap(int[] duplicate,int index1,int index2){
int temp=duplicate[index1];
duplicate[index1]=duplicate[index2];
duplicate[index2]=temp;
}

}

Problem:

Given an input string (s) and a pattern (p), implement regular expression matching with support for ‘.’ and ‘‘.
‘.’ Matches any single character.
‘ Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Note:
s could be empty and contains only lowercase letters a-z.
p could be empty and contains only lowercase letters a-z, and characters like . or .
Example 1:
Input:
s = “aa”
p = “a”
Output: false
Explanation: “a” does not match the entire string “aa”.
Example 2:
Input:
s = “aa”
p = “a

Output: true
Explanation: ‘‘ means zero or more of the preceding element, ‘a’. Therefore, by repeating ‘a’ once, it becomes “aa”.
Example 3:
Input:
s = “ab”
p = “.

Output: true
Explanation: “.*” means “zero or more (*) of any character (.)”.
Example 4:

Input:
s = “aab”
p = “cab”
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore, it matches “aab”.
Example 5:

Input:
s = “mississippi”
p = “misis*p.”
Output: false

Intuition(Recursion):

这一题难度较大,先考虑用递归的思路来解。本题是要求用正则表达式对字符串进行匹配,那么我们就按从左到右的顺序依次扫描,进行匹配。这个时候难点来了,如何处理.和*这两个正则表达式特有字符。

.好理解,能够对任意字符进行匹配。*则是能够让前面的字符出现任意次。这个任意次放到递归里面表达就可以等价看成忽略正则表达式的前面一个字符再与被匹配字符串进行一次匹配。第一种情况放到代码中实际就是被匹配字符串不动,正则表达式下标右移两格,其中移两格而不是一格的原因是要包括星号(isMatch(text, pattern.substring(2)))。

第二种情况就是被匹配表达式右移一格,正则表达式不动(first_match && isMatch(text.substring(1), pattern))。我们可以看到第二种情况不只包括*号前面的字符c出现一次的情况,也能包含c出现任意次的情况。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
public static boolean isMatch(String text, String pattern) {
if (pattern.isEmpty()) return text.isEmpty();
boolean first_match = (!text.isEmpty() &&
(pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.'));

if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
return (isMatch(text, pattern.substring(2)) ||
(first_match && isMatch(text.substring(1), pattern)));
} else {
return first_match && isMatch(text.substring(1), pattern.substring(1));
}
}

Intuition(dynamic programming):

先贴一个讲dynamic programming讲的还可以的链接:https://blog.csdn.net/u013309870/article/details/75193592

所谓dynamic programming,核心也就是要记住自己已经求解过的解。放到本题中,整体思路就是通过dp[i-][j-]来生成dp[s.length][p.length].其中s表示的是被测试字符串,p表示的是正则表达式。i表示的是被测试字符串当前匹配到的下标,j表示的是正则表达式的当前下标。

我们可以看到如果不考虑号,dp[i][j]如果要和dp[i-1][j-1]建立联系其实很简单。只要s[i-1]和p[j-1]一样或者p[j-1]是可以匹配任意字符的.就行了。整个二维矩阵的第一列我们也很清楚,如果被匹配字符串的长度为0,你正则表达式只要不为空,随便匹配啥都应该是false。第一行同理,那么这个时候我们就先生成了一个不考虑正则表达式带号的二维矩阵。

接下来的考验就是加入号后研究整个矩阵的变化。如果正则表达式出现号(出现在j-1的地方),那么要知道dp[i][j]能够为true(成功匹配),只要满足dp[i][j-2]为true或者dp[i-1][j]为true。其中逻辑在上面讲递归逻辑的时候已经做了详细说明。这些工作都做完后,只需要放到for循环中进行遍历题目就出来了。最后说明一下为什么判断条件里面是p.charAt(j-1)而不是p.charAt(j).因为p.charAt(j-1)表示的是正则表达式的第j个字符而不是第j-1个字符,也就是二维数组中第二维的内容。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean isMatch(String s, String p) {
boolean[][] dp = new boolean[s.length()+1][p.length()+1];
dp[0][0] = true;
for (int i=0;i<=s.length();i++) { // i == 0 to fill up i=0 column. in order to handle s="", p="x*"
for (int j=1;j<=p.length();j++) {
if (j-1 >= 0 && p.charAt(j-1) == '*') {
boolean firstMatch = i > 0 && (s.charAt(i-1) == p.charAt(j-2) || p.charAt(j-2) == '.');
dp[i][j] = firstMatch && dp[i-1][j] || dp[i][j-2];
} else {
boolean firstMatch = i > 0 && (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.');
dp[i][j] = firstMatch && dp[i-1][j-1];
}
}
}
return dp[s.length()][p.length()];
}

Problem:

Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward.

Example 1:

Input: 121
Output: true
Example 2:

Input: -121
Output: false
Explanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome.
Example 3:

Input: 10
Output: false
Explanation: Reads 01 from right to left. Therefore it is not a palindrome.

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean isPalindrome(int x) {
if(x < 0){
return false;
}

int a = 0;
int b = x;
while(x > 0){
int mod = x % 10;
a = a * 10 + mod;
x = x / 10;
}

if(b == a){
return true;
}
return false;
}

Problem:

Implement atoi which converts a string to an integer.

The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. Then, starting from this character, takes an optional initial plus or minus sign followed by as many numerical digits as possible, and interprets them as a numerical value.

The string can contain additional characters after those that form the integral number, which are ignored and have no effect on the behavior of this function.

If the first sequence of non-whitespace characters in str is not a valid integral number, or if no such sequence exists because either str is empty or it contains only whitespace characters, no conversion is performed.

If no valid conversion could be performed, a zero value is returned.

Note:

Only the space character ‘ ‘ is considered as whitespace character.
Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−231, 231 − 1]. If the numerical value is out of the range of representable values, INT_MAX (231 − 1) or INT_MIN (−231) is returned.
Example 1:

Input: “42”
Output: 42
Example 2:

Input: “ -42”
Output: -42
Explanation: The first non-whitespace character is ‘-‘, which is the minus sign.
Then take as many numerical digits as possible, which gets 42.
Example 3:

Input: “4193 with words”
Output: 4193
Explanation: Conversion stops at digit ‘3’ as the next character is not a numerical digit.
Example 4:

Input: “words and 987”
Output: 0
Explanation: The first non-whitespace character is ‘w’, which is not a numerical
digit or a +/- sign. Therefore no valid conversion could be performed.
Example 5:

Input: “-91283472332”
Output: -2147483648
Explanation: The number “-91283472332” is out of the range of a 32-bit signed integer.
Thefore INT_MIN (−231) is returned.

Intuition:

这一题的核心逻辑还是边界条件的处理,这部分代码和上一题是一样的。

对任意字符串转换成指定结果类型题目的处理通常str=str.trim()这一步都是少不了的。

接下来就是对字符的ASCII码进行一个简单的处理,也没有特别多可说的地方。

总的来说,熟悉字符串的常规操作以及了解字符串和整数的关系在做这种题目的时候可以省不少时间,而这种题目在考试中也会反复出现,应该引起足够重视。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 	
public int myAtoi(String str) {
str = str.trim();
int cur = 0;
int result = 0;
int flag = 1;
int curNum;
while (cur < str.length()) {
curNum = (int)str.charAt(cur) - '0'; // ASCII
if (curNum <= 9 && curNum >= 0) {
if (result > Integer.MAX_VALUE / 10 || (result == Integer.MAX_VALUE / 10 && curNum > 7))
return Integer.MAX_VALUE;
else if (result < Integer.MIN_VALUE / 10 || (result == Integer.MIN_VALUE / 10 && curNum > 8))
return Integer.MIN_VALUE;
else
result = result * 10 + flag * curNum;
}
else if (cur == 0) {
if (str.charAt(cur) == '-')
flag = -1;
else if (str.charAt(cur) != '+')
break;
}
else
break;
cur++;
}
return result;
}

Problem:

Given a 32-bit signed integer, reverse digits of an integer.

Example 1:

Input: 123
Output: 321
Example 2:

Input: -123
Output: -321
Example 3:

Input: 120
Output: 21
Note:
Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−231, 231 − 1]. For the purpose of this problem, assume that your function returns 0 when the reversed integer overflows.

Intuition:

easy tag,本身要说的内容不多。本题唯一要注意的也就是溢出部分的处理。在java中,integer.MAXVALUE比integer.MINVALUE的绝对值要小1,这个地方如果忽略掉的话就很容易出错。

下列代码中的7是Integer.MAXVALUE除以10的余数。8是Integer.MINVALUE的绝对值除以10的余数。

Solution:

1
2
3
4
5
6
7
8
9
10
11
public int reverse(int x) {
int rev = 0;
while (x != 0) {
int pop = x % 10;
x /= 10;
if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE / 10 && pop > 7)) return 0;
if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE / 10 && pop < -8)) return 0;
rev = rev * 10 + pop;
}
return rev;
}

Problem:

The string “PAYPALISHIRING” is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)

P A H N
A P L S I I G
Y I R
And then read line by line: “PAHNAPLSIIGYIR”

Write the code that will take a string and make this conversion given a number of rows:

string convert(string s, int numRows);
Example 1:

Input: s = “PAYPALISHIRING”, numRows = 3
Output: “PAHNAPLSIIGYIR”
Example 2:

Input: s = “PAYPALISHIRING”, numRows = 4
Output: “PINALSIGYAHRPI”
Explanation:

P I N
A L S I G
Y A H R
P I

Intuition:

这个题目我的思路是这样的。我们只要最后能够返回一个String rows[]的数组,rows[i]表示的就是经过zigzag转换后第i行的字符串问题就能解决了。

那么进一步来说,我们只要按顺序遍历当前字符串,每经过一个字符就算出它经过zigzag转换之后处于新字符串的哪一行就行了。而算行数这个算法本身并不复杂,在纸上一画直接找规律就行。

最后我们根据rows数组重新拼接字符串就能得到答案。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static String convert(String s, int numRows) {
String[] rows=new String[numRows];
for(int i=0;i<numRows;i++){
rows[i]="";
}
for(int i=0;i<s.length();i++){
int row=getRow(i,numRows);
//字符转字符串
rows[row]+=String.valueOf(s.charAt(i));
}
StringBuffer sb=new StringBuffer("");
for(int i=0;i<numRows;i++){
sb.append(rows[i]);
}
return sb.toString();
}
public static int getRow(int index,int numRows){
/*
4 0 0,1 1,2 2,3 3,4 2,5 1
*/
if(numRows==1){
return 0;
}
int ans=index%(2*numRows-2);
if(ans<numRows){
return ans;
}else{
return 2*numRows-2-ans;

}
}

Problem:

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Example 2:

Input: “cbbd”
Output: “bb”

Intuition:

首先我们可以很明显的发现回文串具有对称的特征。既然是对称的,我们在编程的时候就一定要利用这个特征来减少算法复杂度。

因为每个回文串都必须有一个中心,所以我们就可以以回文串中心来作为切入点,开始写循环。需要注意的是回文串有两种形式,第一种是奇数长度的回文串。它以当前回文串中的某个字符为中心。第二种是偶数长度的回文串,它的中心在当前回文串中的字符之间的空字符中。

接下来要做的就是每个中心点返回一个以它或它右边的空字符为中心的最长回文串的长度。这个功能要如何完成呢?

注意到这里满足第三题中提到的滑动窗口的使用条件(给予限制条件,求最大长度类问题),所以直接用滑动窗口完成子方法就可以完成本题。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public String longestPalindromeSolution(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
//这里要考虑两种情况,回文数可能是总长度为奇数或者偶数,那么如果是奇数的话它的中心就都是i,如果是偶数的话它的中心就是i+1
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}

private int expandAroundCenter(String s, int left, int right) {
int L = left, R = right;
while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
L--;
R++;
}
return R - L + 1;
}

Problem:

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

You may assume nums1 and nums2 cannot be both empty.

Example 1:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0
Example 2:

nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

Intuition:

leetcode里遇到的第一道标签为hard的题,然后就直接用暴力方法硬解出来了。跑出来时间复杂度(超过100%)和空间复杂度(超过87.5%)都比标答要好,所以采用原方法。

其实这个题思路很直接,要我找中位数,我只要把算中位数的两个数leftNum,rightNum都算出来即可。下面谈谈比较容易出问题的地方:

1.我们一点一点取数取到中位数的时候很有可能其中一个数组已经被我们取光了。而这个数组是哪个我们事先并不知道。直观上可能会认为短的数组会先被取完,但这个是不对的。如果较短的数组存的都是较大的数,那么长的数组也可能被先取完。所以往哪个数组补元素(本题的应用场景要求补正无穷)必须运行时才能决定。

2.写for循环的时候第二个数是和循环次数相等,而数组下标是从0开始,所以如果我们要从数组的第一个元素跑到数组下标所对应的元素都话,还应该再加1。这个点很容易犯错。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
   public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
double ans = 0;
//leftIndex表示的是中位数中计算平均数时较小的数在s1和s2合并后整个数组中的下标
//leftIndex表示的是中位数中计算平均数时较大的数在s1和s2合并后整个数组中的下标
int totalLength=nums1.length+nums2.length;
int leftIndex=0;
int rightIndex=0;
if(totalLength%2==1){
leftIndex=totalLength/2;
rightIndex=totalLength/2;
}else{
leftIndex=totalLength/2-1;
rightIndex=totalLength/2;
}
int num1Counter=0;
int num2Counter=0;
double leftNum=0;
double rightNum=0;
//第一个坑在这儿,totalCounter<rightIndex+1.因为index是从0开始数多,我们这里需要的是循环的次数,所以要加1
for(int totalCounter=0;totalCounter<rightIndex+1;totalCounter++){
double num1=0;
double num2=0;
//第二个坑在这儿,如果其中一个数组被遍历完了这个时候应该怎么办(我的解决方案就是往后面塞正无穷)
if(num1Counter>nums1.length-1){
num1=POSITIVE_INFINITY;
}else{
num1=nums1[num1Counter];
}
if(num2Counter>nums2.length-1){
num2=POSITIVE_INFINITY;
}else{
num2=nums2[num2Counter];
}
if(totalCounter<leftIndex){
if(num1<num2){
num1Counter++;
}else{
num2Counter++;
}
}else if(totalCounter==leftIndex){
if(num1<num2){
leftNum=num1;
num1Counter++;
}else{
leftNum=num2;
num2Counter++;
}
}else{
if(num1<num2){
rightNum=num1;
num1Counter++;
}else{
rightNum=num2;
num2Counter++;
}
}
//第三个坑在这儿,如果想要统一写成ans=(leftNum+rightNum)/2是不行的,因为在leftindex和rightindex相等的时候
// 它最后根本不会到rightIndex这边,导致rightNum还是默认值
if(totalLength%2==0) {
ans = (leftNum + rightNum) / 2;
}else{
ans=leftNum;
}
}
return ans;
}

Problem:

Given a string, find the length of the longest substring without repeating characters.

Example 1:

Input: “abcabcbb”
Output: 3
Explanation: The answer is “abc”, with the length of 3.
Example 2:

Input: “bbbbb”
Output: 1
Explanation: The answer is “b”, with the length of 1.
Example 3:

Input: “pwwkew”
Output: 3
Explanation: The answer is “wke”, with the length of 3.
Note that the answer must be a substring, “pwke” is a subsequence and not a substring.

Intuition:

这个题目总体来说就是运用了滑动窗口的思想。如果算法题中一旦出现了固定长度求最值或者提供限制条件求最大长度的时候就应该有使用这个算法的意识。

如果是固定长度求最值的问题(譬如求数组中连续k个元素最大的和),那么我们就先指定两个下标i,j(j=i+k-1),对k个元素长的子数组进行求和,然后每次移动i的时候,j也往相同方向移动一格,这样就可以在O(n)的时间复杂度内得到答案。

如果是限制条件求最大长度的问题(譬如本题),只需在每次移动下标的时候用set对新元素进行检查,如果成功就把j往后移,继续扩展数组长度。否则将i往后移看是否能规避冲突。

给一个滑动窗口算法相关的讨论链接:https://www.zhihu.com/question/314669016

以后如果有时间会在博客中讨论这个话题。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static int lengthOfLongestSubstringSolution(String s){
//被答案吊打了。这一题答案采用的是滑动窗口的思想。i表示滑动窗口的头,j表示滑动窗口的尾
//自己之前没有想到这种方法是因为默认就觉得要用for循环遍历字符串,游标必然++,那么一旦冲突不一定是和第一个字符冲突
//用滑动窗口就发现在答案算法中就算不和第一个字符冲突,它i往后移动迟早会碰到冲突部分。
//这题要理解这种算法直接在头脑中想象一个滑动窗口和一个特殊字符串pwwkew就容易了
int n=s.length();
Set<Character> set=new HashSet<>();
int ans=0,i=0,j=0;
while(i<n&&j<n){
if(!set.contains(s.charAt(j))) {
set.add(s.charAt(j++));
ans=Math.max(ans,j-i);
}else{
set.remove(s.charAt(i++));
}
}
return ans;

}