1、(数组)两数之和-简单
题目
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
思路
-
暴力解法
- 时间复杂度O(n^2)
-
使用hashmap,判断目标值-每个值是否存在。
-
定义一个key为数组值,value为对应下标的hashmap,依次遍历数组并在hashmap中找是否含有“遍历值-target”的key,如果含有就说明存在与遍历值之和等于target的数,即可返回遍历值下标与另外一个值下标【hashmap.getvalue(遍历值-target)即可获得】。如果不含则将遍历值与其下标存放进hashmap中,进行下一轮遍历。
-
时间复杂度O(n)
-
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
}
2、(链表)两数相加-中等
题目
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
思路
-
暴力解法
- 分别遍历两条链表将数取出来然后转化为整数,再将相加的整数放入链表,但是当链表长度过长时无法转化为整数
-
对位相加
- 依次取出“个十百千…”对应位相加,并注意是否向后进1。主要是将 (val1 + val2 + add) % 10写进第三条链表中,并且考虑var1+var2+add的值大于9需要进1位,每次循环都要对其判断更新add值。
代码
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode temp1 = l1, temp2 = l2;
int val1 = 0, val2 = 0;
int add = 0, result = 0;
ListNode headNode = new ListNode();
ListNode temp = headNode;
//两条链表都遍历完停止
while(temp1 != null || temp2 != null)
{
//如果某条链表先遍历完,后续就用0代替对应位的值
if(temp1 == null) val1 = 0;else val1 = temp1.val;
if(temp2 == null) val2 = 0;else val2 = temp2.val;
//两个数相加同时也要加上 上一位的“向上进”的数
result = (val1 + val2 + add) % 10;
//将结果放在第三条链表中
temp.val = result;
//大于9向前进1
// System.out.println("result="+result);
if(val1 + val2 + add >= 10) add = 1;else add = 0;
// System.out.println("add="+add);
if(temp1 != null) temp1 = temp1.next;
if(temp2 != null) temp2 = temp2.next;
if(temp1 != null || temp2 != null){
temp.next = new ListNode();
temp = temp.next;
}else{
temp.next = null;
}
}
//如果最后两个数加完大于10,还得在最后补个1
if(add == 1) {
temp.next = new ListNode();
temp.next.val = 1;
temp.next.next = null;
}
return headNode;
}
}
3、(滑动窗口)最长无重复子串-中等(hashmap)
题目
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
思路
-
计算出从每个位置开始往后走能有多长不遇到已经包含了的字符
- 计算出从每个位置开始往后走能有多长不遇到已经包含了的字符,外循环遍历每一个位置,内循环则从该位置开始往后遍历计算出能有多长不遇到重复的,判断前面字符串是否包含新字符使用hashset,全部遍历完后以从每个位置开始都有一个最大无重复子串,找出最大的即可。
- 时间复杂度O(2n)
-
优化思路1:
计算下一个位置最大无重复子串时不需要再从该位置依次向后遍历,只需要将前一个位置字符去掉,直接从上个重复了的字符开始计算接下来的最大无重复子串,因为从前一个字符到判断到出现重复字符这一段肯定没有重复的字符。如下图,最开始计算从第一个位置a开始最大无重复子串依次从a遍历到“abc”,到第4个就出现重复了,接下来计算从第二个位置b开始的最大无重复子串,这时候不要从第二个位置后依次遍历,只需从上次遇到重复字符的第4个位置开始就行了,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KS1EqFIu-1618028768123)(C:\Users\91051\AppData\Roaming\Typora\typora-user-images\image-20210201221102663.png)]
- 时间复杂度为O(n),最坏是O(2n)
3.思路2基础上优化:hashmap同时记录重复字符位置
由于2的方法不能知道前面那个位置为重复字符串,这样再计算重复字符位置之前的位置开始的最大无重复子串就没意义了,因为前面开始的肯定小于上个位置开始的无重复子串长度。向下面这种情况我们就该直接从第4个位置d开始计算了,没必要算b和c位置开始的最大无重复子串了。这时候可以选择使用HashMap来解决,key为字符,value保存(下标+1),如果发现重复直接跳至重复字符后。即 i = Math.max(map.get(s.charAt(j)), i);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9hO0nWhu-1618028768135)(C:\Users\91051\AppData\Roaming\Typora\typora-user-images\image-20210201222324796.png)]
时间复杂度:O(n)
4.思路3另一种实现:使用数组记录重复字符位置
下标作为字符ascll码保存字符,对应值保存字符位置
代码 1
class Solution {
public int lengthOfLongestSubstring(String s) {
int count[] = new int[s.length()];
int maxCount = 0;
HashSet<Character> set = new HashSet<Character>();
if("".equals(s))
return 0;
for(int i = 0; i < s.length(); i++)
{
Character temp = s.charAt(i);
set.clear();
int cnt = 1;
set.add(temp);
for(int j = i + 1; j < s.length(); j++)
{
Character temp2 = s.charAt(j);
if (set.contains(temp2)) {
count[i] = cnt;
break;
}else{
cnt++;
set.add(temp2);
}
}
}
for(int i = 0; i < count.length; i++)
{
if(maxCount < count[i])
maxCount = count[i];
}
return maxCount;
}
}
代码2
class Solution {
public int lengthOfLongestSubstring(String s) {
HashSet<Character> set = new HashSet<Character>();
int maxLen = 0;
int i = 0, j = 0;
while(i < s.length() && j < s.length())
{
if(!set.contains(s.charAt(i))){
//不包含则更新长度并且加入到集合中
maxLen = Math.max(i - j + 1, maxLen);
set.add(s.charAt(i++));
}else{
//包含则位置向前移动一位,并将前面位置元素从集合中删除
set.remove(s.charAt(j++));
}
}
return maxLen;
}
}
代码3
class Solution {
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
int maxLen = 0;
for(int i = 0, j = 0; i < s.length(); i++)
{
if(map.containsKey(s.charAt(i))){
//直接计算重复字符下一个位置的最大无重复子串,与j位置前面的元素重复不算,所有取两者最大的。
j = Math.max(map.get(s.charAt(i)), j);
}
//更新maxLen值,并将字符加入到map中
maxLen = Math.max(i - j + 1, maxLen);
map.put(s.charAt(i), i + 1);
}
return maxLen;
}
}
代码4
class Solution {
public int lengthOfLongestSubstring(String s) {
int maxLen = 0;
int index[] = new int[300];//自动初始化为0
for(int i = 0, j = 0; i < s.length(); i++)
{
//直接计算重复字符下一个位置的最大无重复子串,与j位置前面的元素重复不算,所有取两者最大的。
//不用再判断是否包含重复再移动j的值,不包含数组值为0,包含则为其下标+1,这里直接取两者最大即可。
j = Math.max(index[s.charAt(i)], j);
//更新maxLen值,并将字符保存到数组中
maxLen = Math.max(i - j + 1, maxLen);
index[s.charAt(i)] = i + 1;
}
return maxLen;
}
}
4、(数组)寻找两个正序数组的中位数-困难
题目
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的中位数。
思路
-
暴力解法
直接归并排序找出中位数
- 时间复杂度O(m+n)
两个数组总长度为len,我们就是找第(len+1)/2个数字(len为偶数还要找上后面一个),k=len/2,我们比较两个数组的第 k / 2 个数字,如果 k 是奇数,向下取整。也就是比较第 3 个数字,上边数组中的 4 和 下边数组中的 3 ,如果哪个小,就表明该数组的前 k / 2 个数字都不是第 k 小数字,所以可以排除。也就是 1,2,3 这三个数字不可能是第 7 小的数字,我们可以把它排除掉。将 1349 和 45678910 两个数组作为新的数组进行比较。我们已经排除掉了 3 个数字,就是这 3 个数字一定在最前边,所以在两个新数组中,我们只需要找第 7 - 3 = 4 小的数字就可以了,也就是 k = 4 。此时两个数组,比较第 2 个数字,3 < 5,所以我们可以把小的那个数组中的 1 ,3 排除掉了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UZJlB6GA-1618028768138)(C:\Users\91051\AppData\Roaming\Typora\typora-user-images\image-20210202175019421.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZIab4Shz-1618028768140)(C:\Users\91051\AppData\Roaming\Typora\typora-user-images\image-20210202194711118.png)]
- 时间复杂度O(log(m+n))
代码1
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int i = 0, j = 0;
double result = 0;
ArrayList<Integer> nums3 = new ArrayList<>();
while(i < nums1.length && j < nums2.length) {
if(nums1[i] <= nums2[j]) {
nums3.add(nums1[i++]);
}else{
nums3.add(nums2[j++]);
}
}
while(i < nums1.length){
nums3.add(nums1[i++]);
}
while (j < nums2.length){
nums3.add(nums2[j++]);
}
// System.out.println("size:"+nums3.size());
if(nums3.size() % 2 == 0){
result = ( nums3.get(nums3.size()/2 - 1) + nums3.get(nums3.size()/2) ) / 2.0;
}else{
result = nums3.get(nums3.size()/2);
}
// System.out.println("result:"+result);
return result;
}
}
代码2
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length, length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
double median = getKthElement(nums1, nums2, midIndex + 1);
return median;
} else {
int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}
public int getKthElement(int[] nums1, int[] nums2, int k) {
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/
int length1 = nums1.length, length2 = nums2.length;
int index1 = 0, index2 = 0;
int kthElement = 0;
while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}
// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
}
5、最长回文子串-中等(动态规划)
题目
给你一个字符串 s
,找到 s
中最长的回文子串。
思路
-
暴力解法
-
判断每个子串是否是回文串,若是且长度大于之前的则保存。
-
时间复杂度O(n^3)
-
leetcode超出时间限制,无法通过!
-
-
简单优化暴力解法
-
1.如果子串长度小于等于之前已经得到的回文子串长度就不用判断该子串是否是回文串了。
2.更新最长回文子串时只需记录子串开始下标和长度。
3.判断字符串是不是回文串时别用StringBuffer的reverse方法来对比,采用两个变量从字符串头和尾开始比较字符是否相等,同时向中间靠拢,如果一直相等则是回文串。
-
时间复杂度O(n^3)
-
实际执行时间比第一种短,可以通过leetcode!
3.动态规划-递归自顶向下
-
arr【i】【j】表示原字符串[i, j]是否是回文串,判断str是否是回文串,可以先判断头尾字符是否相等,不等直接返回false,相等的话再判断其长度,若长度小于4,即除了头尾最多还剩中间一个,这时候肯定是回文串,返回true,若长度大于等于4则判断arr【i+1】【j-1】是不是回文串。在主函数用二位arr数组保存好对应值。
-
时间复杂度O(n^2)
4.动态规划-迭代自底向上
- 注意初始化状态数组arr的值必须从最小开始,看代码4
- 时间复杂度O(n^2)
-
代码1
class Solution {
public String longestPalindrome(String s) {
int max = 0;
String result = null;
if(s == null || s.equals(""))
return "";
for(int i = 0; i < s.length(); i++){
for(int j = i+1; j <= s.length(); j++){
if(isStr(s.substring(i,j)) && max < j - i){
result = s.substring(i,j);
max = j - i;
}
}
}
return result;
}
public static Boolean isStr(String s){
StringBuffer temp = new StringBuffer(s);
temp.reverse();
if(s.equals(temp.toString())){
return true;
}
return false;
}
}
代码2
class Solution {
public String longestPalindrome(String s) {
int len = 0, index = 0;
if(s == null || s.equals(""))
return "";
for(int i = 0; i < s.length(); i++){
for(int j = i+1; j <= s.length(); j++){
if(j - i > len && isStr(s.substring(i,j))){
index = i;
len = j - i;
}
}
}
return s.substring(index, index+len);
}
public static Boolean isStr(String s){
for(int i = 0, j = s.length() - 1; i <= j; i++, j--){
if(s.charAt(i) != s.charAt(j)){
return false;
}
}
return true;
}
}
代码3
public static String longestPalindrome(String s) {
int len = 0, index = 0;
int[][] flag = new int[s.length()][s.length()];
if(s == null || s.equals(""))
return "";
for(int i = 0; i < s.length(); i++){
for(int j = i; j < s.length(); j++){
if( j - i + 1 > len ){
if(i == j || flag[i][j] == 1) {
len = j - i + 1;
index = i;
}else{
if(flag[i][j] == -1)
continue;
if(isStr(s, i, j)){
len = j - i + 1;
index = i;
flag[i][j] = 1;
}else{
flag[i][j] = -1;
}
}
}
}
}
return s.substring(index, index+len);
}
public static Boolean isStr(String s, int startIndex, int endIndex){
if(s.charAt(startIndex) != s.charAt(endIndex))
return false;
else {
if(endIndex - startIndex + 1 < 4)
return true;
else
return isStr(s, startIndex+1, endIndex-1);
}
}
代码4
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if(len < 2) return s;
int begin = 0, max = 1;
boolean[][] dp = new boolean[len][len];
char[] charArray = s.toCharArray();
//从最底开始初始化
for(int j = 0; j < len; j++) {
for(int i = 0; i <= j; i++) {
if(charArray[i] == charArray[j]) { // 不用对dp数组填充false
if(j - i < 3) dp[i][j] = true;
else dp[i][j] = dp[i + 1][j - 1];
}
if(dp[i][j] && j - i + 1 > max) {
begin = i;
max = j - i + 1;
}
}
}
return s.substring(begin, begin + max);
}
}
6、(数组)Z字形变化-中等
题目
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
思路
-
按部就班coding就OK,这题主要考编写代码能力
- 难点1:怎么去存放“Z字形”?可以采用元素为StringBuffer对象的一维数组来存放。直接在需要的行(数组的第几个元素就代表第几行)里append字符就行。
- 难点2:怎么实现在一维数组里来回循环?,假设需要4行即数组长度为4:即0->1->2->3->2->1->0->1->2->3->2…:。可以使用一个标志变量flag来标志循环下标J是否已经循环到头或尾,到了头或尾循环下标j就变为j–或j++。
- 这里得注意行数为1,即数组长度为1时,直接返回原字符串就OK,不要下面循环,不然 j -= 2 后 j 的值不再下标范围内了,参考代码思考。
代码1
class Solution {
public String convert(String s, int numRows) {
StringBuffer arr[] = new StringBuffer[numRows];
int flag = 0;
StringBuffer result = new StringBuffer("");
if(numRows == 1){
return s;
}
for(int i = 0; i < arr.length; i++){
arr[i] = new StringBuffer("");
}
for(int i = 0, j = 0; i < s.length(); i++){
arr[j].append(s.charAt(i));
if(flag == 0){
j++;
}else{
j--;
}
//到尾了flag变化,接下来j执行j--
if(j == arr.length){
flag = 1;
j -= 2;//使j等于倒过来循环的开始下标
}
//又回到头了,flag变化,接下来j执行j++
if(j == -1){
flag = 0;
j += 2;
}
}
for(int i = 0; i < arr.length; i++){
result.append(arr[i]);
}
return result.toString();
}
}
7、整数反转-简单
题目
给你一个 32 位的有符号整数 x ,返回 x 中每位上的数字反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
思路
-
暴力解法
- 字符串反转
-
模10运算得个位数,整除去掉个位数。
-
关键语句:ans = ans * 10 + x % 10; x = x / 10;
-
时间复杂度O(lg(x))
-
代码1
public static int reverse(int x) {
StringBuffer num = new StringBuffer(x+"");
int number = 1;
if(num.charAt(0) == '-'){
System.out.println(num);
num = new StringBuffer(num.substring(1, num.length()));
System.out.println(num);
number = -1;
}
num.reverse();
try {
number *= Integer.parseInt(num.toString());
}catch (Exception e){
return 0;
}
return number;
}
代码2
class Solution {
public int reverse(int x) {
int ans = 0;
while (x != 0) {
//ans*10超出整数范围
if(ans * 10 / 10 != ans){
ans = 0;
break;
}
//ans等于上次循环值*10加上新的余数
ans = ans * 10 + x % 10;
//去掉x个位数
x = x / 10;
}
return ans;
}
}
8、(字符串)字符串转整数-中等
题目
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
读入字符串并丢弃无用的前导空格
检查第一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
返回整数作为最终结果。
思路
-
-
这个题考查细节处理、认真读题。是日常开发中对于原始数据的处理(例如「参数校验」等场景),如果面试中遇到类似的问题,应先仔细阅读题目文字说明和示例,有疑惑的地方和需要和面试官确认,在编码的时候需要耐心和细心地调试。
-
时间复杂度O(n)
-
代码
class Solution {
public int myAtoi(String s) {
int len = s.length();
int startIndex = 0, symbol = 1;
long result = 0;
//找到不是空格的第一个字符开始
for(int i = 0; i < len; i++){
if(s.charAt(i) != ' '){
startIndex = i;
break;
}
}
for(int i = startIndex; i < len; i++){
//第一个字符确定符号
if(i == startIndex) {
if (s.charAt(i) == '-') {
symbol = -1;
continue;
}
if (s.charAt(i) == '+') {
symbol = 1;
continue;
}
}
//不是数字直接返回结果
if(s.charAt(i) < '0' || s.charAt(i) > '9'){
return (int)result * symbol;
}else
{
result = result * 10 + (s.charAt(i) - '0');
if(result * symbol < -2147483648){
return -2147483648;
}
if(result * symbol > 2147483647){
return 2147483647;
}
}
}
return (int)result * symbol;
}
}
9、回文数-简单(双指针)
题目
给你一个整数 x
,如果 x
是一个回文整数,返回 ture
;否则,返回 false
。
思路
-
双指针向中间靠拢,一旦有一个不等就不是,靠拢完返回true
时间复杂度O(n)
代码
class Solution {
public boolean isPalindrome(int x) {
String s = x+"";
for(int i = 0, j = s.length() - 1; i <= j; i++, j--){
if(s.charAt(i) != s.charAt(j)){
return false;
}
}
return true;
}
}
10、正则表达式匹配-困难(动态规划)
题目
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
’.’ 匹配任意单个字符
’*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
思路
-
递归压栈
-
注意:读懂这题的意思,最开始我一直误解这个题了,不仅导致我自己做不出来而且看别人的代码还看不懂,我起初把星号的意思理解为标准的正则表达式里的意思了,也就是匹配任意字符,但这个题的意思是匹配字符串中星号前面一定有一个字符,表示可以匹配这个字符零个或多个。
-
isMatch2(String s, String p, int i, int j)//表示p的[0,j]能否匹配s[0,i]
要考虑X+*可以匹配多个可以匹配零个,例如:
//aaa与aa*a这里的*a匹配了一个a;aa与aa*a这里的*a匹配了0个a;baaa与baa*这里的*a匹配了两个a
所以:如果X与是[i]相等的话我们要把X+*能匹配的个数都试一遍,代码即:
Boolean duoge = false; //需要判断第X这个字符是否相等s[i]才决定是否匹配多个,不判断的话 //可能会跳过本来该匹配的字符。 Boolean secondChar = j > 0 && i >= 0 && (p.charAt(j-1) == s.charAt(i) || p.charAt(j-1) == '.'); if(secondChar){ duoge = isMatch2(s,p,i-1,j); } return isMatch2(s,p,i,j-2) || duoge;
- 时间复杂度有点难计算,主要是遇到X+*时:需要挨着递归验证匹配0次到匹配最多次到底匹配几次能行,因为你不知道前面字符的情况。所以:text中X越多,时间消耗越大
2.动态规划
//和1一样,用d[i][j]表示pattern的前j个字符能否匹配text的前i个字符 //转移方程如下: /* p[j] == t[i]或p[j] == '.'则:dp[i][j] = dp[i-1][j-1]。这个容易理解和想到 ** p[j] == ‘*’时比较难想出来,这时候主要考虑两种情况,匹配0次和匹配多次 ** 匹配0次:如果p[j-1] != t[i]肯定匹配0次 ** 匹配多次:前提p[j-1] == t[i],这时候:dp[i][j] = dp[i-1][j] */ //迭代初始化:可知dp[0][j]为true,然后开始迭代
-
代码1
class Solution {
public boolean isMatch(String text, String pattern) {
return isMatch2(text, pattern, text.length()-1, pattern.length()-1);
}
//表示p的前j+1个字符能否匹配s的前i+1个字符
public static boolean isMatch2(String s, String p, int i, int j) {
//如果j=0了,即匹配字符用完了,就直接看待匹配的字符串是否相应
//匹配完了,如果匹配完了说明刚好匹配成功,反之就匹配失败
if(j < 0){
if(i < 0) return true;
else return false;
}
/*考虑p[j]是否为*
1.是:'X+*'考虑两种情况
1.1匹配零个,即X这字符不等于s[i]:跳过‘字符+*’这两个字符,继续判断isMatch2(s, p, i, j-2)
1.2匹配多个,即X这字符等于s[i]:就判断isMatch2(s,p,index,j-2),index为一直往后走到s[index]!=p[j-1]的位置
2.不是:直接返回第一个字符比较结果与isMatch2(s,p,i-1,p-1)
*/
if(p.charAt(j) == '*'){
Boolean duoge = false;
//需要判断第X这个字符是否相等s[i]才决定是否匹配多个,不判断的话
//可能会跳过本来应该匹配的字符。
Boolean secondChar = j > 0 && i >= 0 && (p.charAt(j-1) == s.charAt(i) || p.charAt(j-1) == '.');
if(secondChar){
duoge = isMatch2(s,p,i-1,j);
}
return isMatch2(s,p,i,j-2) || duoge;
}
//不为*就判断第一个字符是否相等
Boolean firstChar = i >= 0 && (p.charAt(j) == s.charAt(i) || p.charAt(j) == '.');
return firstChar && isMatch2(s, p, i - 1, j - 1);
}
}
代码2
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][] f = new boolean[m + 1][n + 1];
f[0][0] = true;
for (int i = 0; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p.charAt(j - 1) == '*') {
f[i][j] = f[i][j - 2];
if (matches(s, p, i, j - 1)) {
f[i][j] = f[i][j] || f[i - 1][j];
}
} else {
if (matches(s, p, i, j)) {
f[i][j] = f[i - 1][j - 1];
}
}
}
}
return f[m][n];
}
public boolean matches(String s, String p, int i, int j) {
if (i == 0) {
return false;
}
if (p.charAt(j - 1) == '.') {
return true;
}
return s.charAt(i - 1) == p.charAt(j - 1);
}
}
11、盛最多水的容器-中等(双指针)
题目
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
示例 1:
。
思路
-
暴力解法,枚举所有可能性找出最大的
- 时间复杂度O(n^2)
-
巧解
从第一条竖线和最后一根竖线开始向中间靠拢,靠拢规则是:每次循环,短的向长的靠拢一步,重新计算两者面积并更新max,直到两边靠拢撞一起。该理论详细证明可百度,简单理解就是:因为高由短的决定,向短靠拢长度减小面积只会降低,所有只有短的向长的靠拢。
代码2
class Solution {
public int maxArea(int[] height) {
int max = 0;
for(int i = 0, j = height.length - 1; i < j;){
if(height[i] < height[j]){
max = Math.max(max, (j-i) * height[i]);
i++;
}else{
max = Math.max(max, (j-i) * height[j]);
j--;
}
}
return max;
}
}
12、整数转罗马数字-中等
题目
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
思路
- 暴力解法,从最大的特定数字开始比较,大于特定数字就append上对应的罗马字符同时减去特定数字大小。
代码2
public String intToRoman(int num) {
int[] values = {1000,900,500,400,100,90,50,40,10,9,5,4,1};
String[] strs = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
StringBuilder sb = new StringBuilder();
for(int i=0;i<values.length;i++) {
while(num >= values[i]) {
num -= values[i];
sb.append(strs[i]);
}
}
return sb.toString();
}
13、罗马数字转整数-简单
题目
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
思路
-
暴力解法,挨着来,特殊的按特殊处理,不过代码繁琐
- 时间复杂度O(n)
-
主要是优化怎么考虑特殊情况
-
可以思考不考虑特殊情况与考虑了的差别,
然后全都不考虑特殊情况直接一遍遍历然后再加上有特殊情况的变化,例如:不考虑特殊情况:IV = 6,考虑:IV=4.所以if(s.indexOf(“IV”)!=-1){sum-=2;},其它的同理。
-
代码2
class Solution {
public int romanToInt(String s) {
int sum=0;
if(s.indexOf("IV")!=-1){sum-=2;}
if(s.indexOf("IX")!=-1){sum-=2;}
if(s.indexOf("XL")!=-1){sum-=20;}
if(s.indexOf("XC")!=-1){sum-=20;}
if(s.indexOf("CD")!=-1){sum-=200;}
if(s.indexOf("CM")!=-1){sum-=200;}
char c[]=s.toCharArray();
int count=0;
for(;count<=s.length()-1;count++){
if(c[count]=='M') sum+=1000;
if(c[count]=='D') sum+=500;
if(c[count]=='C') sum+=100;
if(c[count]=='L') sum+=50;
if(c[count]=='X') sum+=10;
if(c[count]=='V') sum+=5;
if(c[count]=='I') sum+=1;
}
return sum;
}
}
14、最长公共前缀-简单
题目
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""
。
思路
-
暴力解法,找个最短的字符串依次将每个字符与其它每个字符串依次比较。
- 时间复杂度O(最短字符长度*字符串数组长度)
代码1
class Solution {
public String longestCommonPrefix(String[] strs) {
int minIndex = 0;
String result = "";
if(strs == null || strs.length == 0)
return "";
//找到长度最小的字符串位置
for(int i = 0; i < strs.length; i++){
if(strs[minIndex].length() > strs[i].length()){
minIndex = i;
}
}
//从最短的字符串第一个字符开始与其他字符串比较
for(int i = 0; i < strs[minIndex].length(); i++){
//与所有字符串第i个位置字符比较
for(int j = 0; j < strs.length; j++){
if(strs[j].charAt(i) != strs[minIndex].charAt(i)){
return result;
}
}
result += strs[minIndex].charAt(i);
}
return result;
}
}
15、三个数之和-中等(双指针)
题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路
-
用HashSet判断两数之和思路来做
-
定义三个指针,i,j,k。遍历i,那么这个问题就可以转化为在i之后的数组中寻找nums[j]+nums[k]=-nums[i]这个问题,也就将三数之和问题转变为二数之和(使用双指针最好),但我自己做的时候用的是leetcode第1题的思路解决两个数之和,用这种方法即使不排序时间复杂度也是O(N^2),但是事先排序不好去掉重复的。想要好点去掉重复的必须得事先将nums排好序。
-
时间复杂度:O(N^2)
-
-
用双指针法
-
L = i + 1, R = len - 1;和为:若三个数和大于 0,说明 nums[R] 太大,R左移;若和小于 0,说明 nums[L太小,L右移。
注意最外层i要跳过重复值,等于0后R和L也要跳过重复值
-
时间复杂度:O(N^2)
-
代码1
List<List<Integer>> lists = new ArrayList<>();
HashSet<Integer> set2 = null;
Arrays.sort(nums);
for(int i = 0; i < nums.length; i++){
if( i == 0 || (i > 0 && nums[i] != nums[i-1]) ) {
set2 = new HashSet<>();
for(int j = i + 1; j < nums.length; j++){
if(!set2.contains(-1*nums[j]+nums[i]*-1)){
set2.add(nums[j]);
}else {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(-1 * nums[j] + nums[i] * -1);
list.add(nums[j]);
lists.add(list);
int temp = nums[j];
//不用nums[j] == nums[j+1],因为如果j一直加到length-1会导致j+1越界。
while (j < nums.length && nums[j] == temp)j++;
j--;
}
}
}
}
return lists;
代码2
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
//排序
Arrays.sort(nums);
int len = nums.length;
for(int i = 0;i < len;++i) {
//跳过重复的
if(nums[i] > 0) return lists;
if(i > 0 && nums[i] == nums[i-1]) continue;
int curr = nums[i];
//定义左右指针
int L = i+1, R = len-1;
while (L < R) {
int tmp = curr + nums[L] + nums[R];
if(tmp == 0) {
List<Integer> list = new ArrayList<>();
list.add(curr);
list.add(nums[L]);
list.add(nums[R]);
lists.add(list);
//跳过重复的
while(L < R && nums[L+1] == nums[L]) ++L;
while (L < R && nums[R-1] == nums[R]) --R;
++L;
--R;
} else if(tmp < 0) {
++L;
} else {
--R;
}
}
}
return lists;
}
16、最接近三数之和-中等(双指针)
题目
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
思路
-
与上一题同样思路,只不过多了记录没次和的结果与target的距离并更新。
- 时间复杂度O(n^2)
代码
class Solution {
public int threeSumClosest(int[] nums, int target) {
int result = 0, len = nums.length;
//初始化result
for(int i = 0; i < len && i < 3; i++)
result += nums[i];
if(len <= 3) return result;
//将nums从小到大排序
Arrays.sort(nums);
for(int i = 0; i < len - 2; i++){
//重复的可以跳过
if(i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1;
int right = len - 1;
while(left < right){
int sum = nums[left] + nums[right] + nums[i];
if(sum == target) return target;
if(Math.abs(sum-target) < Math.abs(result-target)) result = sum;
if(sum < target) left++;
if(sum > target) right--;
}
}
return result;
}
}
17、电话号码的字符组合-中等(回溯)
题目
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
思路
-
暴力,看看就好
- 时间复杂度O(n^len)
-
这是一道很基础的回溯问题
- 时间复杂度O(3^n)
代码1
public static List<String> letterCombinations(String digits) {
List<String> list = new ArrayList<>();
if(digits == null || digits.equals("")) return list;
int len = digits.length();
//flag[number - 2]就是对应字母串
String[] flag = new String[]{"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
//将数字字符串转为数字数组方便后面算
int[] digitArr = new int[5];
for(int i = 0; i < len; i++){
digitArr[i] = Integer.parseInt(digits.charAt(i)+"");
}
//随便用个字母初始化一下
StringBuffer str = new StringBuffer("");
for(int i = 0; i < len; i++){
str.append("a");
}
//既然长度最大为4,嘿嘿,看我暴力无限套娃大法,只要你敢给,我就敢套
for(int i = 0; i < flag[digitArr[0]-2].length(); i++){
str.replace(0, 1, flag[digitArr[0]-2].charAt(i)+"");
if(len == 1){
list.add(str.substring(0, 1));
}
for(int j = 0; digitArr[1]-2 >= 0 && j < flag[digitArr[1]-2].length(); j++){
if(len > 1){
str.replace(1, 2, flag[digitArr[1]-2].charAt(j)+"");
if(len == 2){
list.add(str.substring(0, 2));
}
}
else{
break;
}
for(int k = 0; digitArr[2]-2 >= 0 && k < flag[digitArr[2]-2].length(); k++){
if(len > 2){
str.replace(2, 3, flag[digitArr[2]-2].charAt(k)+"");
if(len == 3){
list.add(str.substring(0, 3));
}
}
else{
break;
}
for(int t = 0; digitArr[3]-2 >= 0 && t < flag[digitArr[3]-2].length(); t++){
if(len > 3){
str.replace(3, 4, flag[digitArr[3]-2].charAt(t)+"");
if(len == 4){
list.add(str.substring(0, 4));
}
}
else{
break;
}
}
}
}
}
return list;
}
代码2
class Solution {
public List<String> letterCombinations(String digits) {
List<String> list1 = new ArrayList<>();
if(digits == null || digits.equals("")) return list1;
int len = digits.length();
//将数字字符串转为数字数组方便后面算
int[] digitArr = new int[len];
for(int i = 0; i < len; i++){
digitArr[i] = Integer.parseInt(digits.charAt(i)+"");
}
f("", digitArr, 0);
list1 = list2;
return list1;
}
//flag[number - 2]就是对应字母
public String[] flag = new String[]{"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> list2 = new ArrayList<>();
public void f(String conbination, int[] number, int numberIndex){
if(numberIndex == number.length){
list2.add(conbination);
return;
}
for(int i = 0; i < flag[number[numberIndex]-2].length(); i++){
f(conbination+flag[number[numberIndex]-2].charAt(i), number, numberIndex+1);
}
}
}
18、(数组)四数之和-中等(双指针)
题目
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
思路
-
与三数之和思路完全一样,就是多加了个循环
- 时间复杂度O(n^3)
代码
List<List<Integer>> lists = new ArrayList<>();
if(nums == null || nums.length == 0) return lists;
Arrays.sort(nums);
int len = nums.length;
for(int index1 = 0; index1 < len - 3; index1++){
//跳过重复的
if(index1 > 0 && nums[index1] == nums[index1-1])
continue;
for(int index2 = index1 + 1; index2 < len - 2; index2++){
//跳过重复的
if(index2 > index1 + 1 && nums[index2] == nums[index2-1])
continue;
int left = index2 + 1;
int right = len - 1;
while(left < right) {
int sum = nums[index1] + nums[index2] + nums[left] + nums[right];
if (sum < target) {
left++;
}
if (sum > target) {
right--;
}
if (sum == target) {
List<Integer> list = new ArrayList<>();
list.add(nums[index1]);
list.add(nums[index2]);
list.add(nums[left]);
list.add(nums[right]);
lists.add(list);
//跳过重复的
while (left < right && nums[left] == nums[left + 1]) left++;
while (right > left && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
}
return lists;
19、删除链表的倒数第N个结点-中等
题目
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
思路
-
暴力
先遍历一遍得到长度,再从头遍历找到倒数第n个
- 时间复杂度O(n)
-
双指针
- 我们可以设想假设设定了双指针 p 和 q 的话,当 q 指向末尾的 NULL,p 与 q 之间相隔的元素个数为 n 时,那么删除掉 p 的下一个指针就完成了要求。
- 时间复杂度O(n)
代码1
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
int len = 0;
ListNode node = head;
//加个头结点在链表前面就可以不用考虑如果删去原节点的头结点这个特殊情况了
ListNode newHead = new ListNode(0, head);
if(head == null) return null;
//计算链表长度
while (node != null){
node = node.next;
len++;
}
//遍历链表直到等于倒数第n个
node = newHead;
ListNode before = null;
int i = len+1;
while (i > 0){
before = node;
node = node.next;
i--;
//找到倒数第n个,把上一个结点指向现在node的下一个结点
if(i == n){
before.next = node.next;
break;
}
}
return newHead.next;
}
}
代码2
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode pre = dummy;
ListNode left = head;
ListNode right = head;
for(int i=0;i<n;i++){
right = right.next;
}
while(fast!=null){
pre = pre.next;
left = left.next;
right = right.next;
}
pre.next = left.next;
return dummy.next;
}
}
20、有效的括号-简单
题目
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
思路
-
使用栈stack
- 时间复杂度O(n)
代码
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
if(s == null || s.equals("")) return false;
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);
if(ch == '(' || ch == '{' || ch == '['){
stack.push(ch);
}else{
if(stack.empty()) return false;
char temp = stack.peek();
if(ch == ')'){
if(temp == '(') stack.pop();
else return false;
}
if(ch == ']'){
if(temp == '[') stack.pop();
else return false;
}
if(ch == '}'){
if(temp == '{') stack.pop();
else return false;
}
}
}
if(stack.empty()) return true;
else return false;
}
21、合并链表-简单(递归)
题目
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路
1.暴力迭代合并
2.递归
时间复杂度:O(n)
代码1
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode newHead = new ListNode(0, null);
ListNode temp1 = l1, temp2 = l2, temp = newHead;
while(temp1 != null && temp2 != null){
if(temp1.val <= temp2.val){
temp.next = temp1;
temp1 = temp1.next;
}else{
temp.next = temp2;
temp2 = temp2.next;
}
temp = temp.next;
}
if (temp1 != null) temp.next = temp1;
else temp.next = temp2;
return newHead.next;
}
代码2
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
22、括号生成-中等(回溯)
题目
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
思路
1.暴力
找出所有序列,筛选出符合条件的
2.递归回溯
时间复杂度:O(n)
代码2
class Solution {
public List<String> generateParenthesis(int n) {
flashBack("", n, n);
return list2;
}
private List<String> list2 = new ArrayList<>();
public void flashBack(String str, int left, int right){
//左右括号全部遍历完表示回溯结束
if(left == 0 && right == 0){
list2.add(str);
return;
}
//剩余括号中左括号始终小于等于右括号的数量
//1.等于就只能跟左括号,小于则两种情况
if(left == right){
flashBack(str+"(", left-1, right);
}else{
flashBack(str+")", left, right-1);
if(left > 0)
flashBack(str+"(", left-1, right);
}
}
}
23、合并k个有序链表-困难
题目
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
思路
-
归并排序
- 时间复杂度O(kn log k)
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
return merge(lists, 0, lists.length - 1);
}
private ListNode merge(ListNode[] lists, int left, int right) {
if (left == right) return lists[left];
int mid = left + (right - left) / 2;
ListNode l1 = merge(lists, left, mid);
ListNode l2 = merge(lists, mid + 1, right);
return mergeTwoLists(l1, l2);
}
private ListNode mergeTwoLists(ListNode node1, ListNode node2) {
ListNode head = new ListNode(0, null);
ListNode temp1 = node1;
ListNode temp2 = node2;
ListNode temp3 = head;
while(temp1 != null && temp2 != null){
if(temp1.val <= temp2.val){
temp3.next = temp1;
temp1 = temp1.next;
}else {
temp3.next = temp2;
temp2 = temp2.next;
}
temp3 = temp3.next;
}
if(temp1!=null) temp3.next = temp1;
else temp3.next = temp2;
return head.next;
}
}