第一题:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
解题思路 :
使用双层for嵌套进行遍历查询 nums[i]=target-nums[j]
题目解答:
public static int[] twoSum(int nums[],int target){
for(int i=0;i<nums.length;i++){
for(int j=1;j<nums.length;j++){
if(nums[i]==target-nums[j]){
return new int []{i,j};
}
}
}
throw new IllegalArgumentException("No two sum solution ");
}
第二题
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
解题思路:
首先建立一个新的链表,然后从头往后撸给的这两个链表,建立一个dummy结点(哑结点),作用是防止这两个链表都为空,保证新建的这个链表存在,由于dummy结点本身不变,所以我们用一个指针cur来指向新链表的最后一个结点(因为是一个一个结点往里加的),这道题最高位在链表的末尾(还有一种情况最高位在链表的开头,一个类型,下个题给出),然后进行while循环(条件是两个链表有一个不为空)因为链表可能为空,所以我们取结点值的时候判断一下,如果为空取0,否则取结点值即可,同时还要加上一个进位carry,然后更新carry,直接sum/10即可然后 用sum%10建立一个新的结点,(不用考虑两数和大于10,因为大于10就会随着carry的更新进位给高位),连到cur的后面,然后移动cur到下一个结点,while循环退出以后,还要进行一次判断就是最高位在进行相加的时候是否进位了,
若carry的值为1,在建立一个为1的结点.
public ListNode addTwonumbers(ListNode l1,ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
int carry = 0;
while (l1 != null || l2 != null) {
int d1 = l1 == null ? 0 : l1.val;
int d2 = l2 == null ? 0 : l2.val;
int sum = d1 + d2 + carry;
carry = sum >= 10 ? 1 : 0;
cur.next = new ListNode(sum % 10);
cur=cur.next;
if (l1 != null) l1 = l1.next;
if (l2 != null) l2 = l2.next;
}
if (carry == 1) cur.next = new ListNode(1);
return dummy.next;
}
第二题进阶
解题思路:
上面的这道题最高位在链表的末尾,而这道题无非就是倒置一下链表,然后在执行上面一样的操作,下面介绍一下如何倒置链表 ,思路是在原链表之前建立一个空的newhead,因为首结点会变,然后从head开始,将之后的下一个结点移动到newhead之后,重复此操作直到head成为末结点为止
public ListNode addTwonumbers(ListNode l1,ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
int carry = 0;
while (l1 != null || l2 != null) {
int d1 = l1 == null ? 0 : l1.val;
int d2 = l2 == null ? 0 : l2.val;
int sum = d1 + d2 + carry;
carry = sum >= 10 ? 1 : 0;
cur.next = new ListNode(sum % 10);
cur=cur.next;
if (l1 != null) l1 = l1.next;
if (l2 != null) l2 = l2.next;
}
if (carry == 1) cur.next = new ListNode(1);
return reverseList(dummy.next);
}
public static ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode cur = dummy;
while (cur.next != null) {
ListNode tmp = cur.next;
tmp.next = dummy.next;
dummy.next = tmp;
}
return dummy.next;
}
第3题
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
解题思路:
我们先不考虑代码怎么实现,如果给一个例子中的例子"abcabcbb",让你手动找无重复字符的子串,该怎么找。博主会一个字符一个字符的遍历,比如a,b,c,然后又出现了一个a,那么此时就应该去掉第一次出现的a,然后继续往后,又出现了一个b,则应该去掉一次出现的b,以此类推,最终发现最长的长度为3。所以说,我们需要记录之前出现过的字符,记录的方式有很多,最常见的是统计字符出现的个数,但是这道题字符出现的位置很重要,所以我们可以使用HashMap来建立字符和其出现位置之间的映射。进一步考虑,由于字符会重复出现,到底是保存所有出现的位置呢,还是只记录一个位置?我们之前手动推导的方法实际上是维护了一个滑动窗口,窗口内的都是没有重复的字符,我们需要尽可能的扩大窗口的大小。由于窗口在不停向右滑动,所以我们只关心每个字符最后出现的位置,并建立映射。窗口的右边界就是当前遍历到的字符的位置,为了求出窗口的大小,我们需要一个变量left来指向滑动窗口的左边界,这样,如果当前遍历到的字符从未出现过,那么直接扩大右边界,如果之前出现过,那么就分两种情况,在或不在滑动窗口内,如果不在滑动窗口内,那么就没事,当前字符可以加进来,如果在的话,就需要先在滑动窗口内去掉这个已经出现过的字符了,去掉的方法并不需要将左边界left一位一位向右遍历查找,由于我们的HashMap已经保存了该重复字符最后出现的位置,所以直接移动left指针就可以了。我们维护一个结果res,每次用出现过的窗口大小来更新结果res,就可以得到最终结果啦。
这里我们可以建立一个HashMap,建立每个字符和其最后出现位置之间的映射,然后我们需要定义两个变量res和left,其中res用来记录最长无重复子串的长度,left指向该无重复子串左边的起始位置的前一个,由于是前一个,所以初始化就是-1,然后我们遍历整个字符串,对于每一个遍历到的字符,如果该字符已经在HashMap中存在了,并且如果其映射值大于left的话,那么更新left为当前映射值。然后映射值更新为当前坐标i,这样保证了left始终为当前边界的前一个位置,然后计算窗口长度的时候,直接用i-left即可,用来更新结果res。
//用hashset解决
public int lengthOfLongestSubstring1(String s) {
//窗口的左边界
int left=0;
//窗口的右边界
int right=0;
int res=0;
HashSet<Character>t=new HashSet<Character>();
//如果维护的这个窗口的右边界小于s.length,并且Hashset里面没有加过这个元素
while(right<s.length()){
if(!t.contains(s.charAt(right))){
t.add(s.charAt(right++));
//更新res的值为最大的窗口移动的范围
res=Math.max(res,t.size());
}else {
//删除重复的元素
t.remove(s.charAt(left++));
}
}
return res;
}
第四题
题目:给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
解题思路
这道题让我们求两个有序数组的中位数,而且限制了时间复杂度为 O(log (m+n)),看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。但是这道题被定义为Hard也是有其原因的,难就难在要在两个未合并的有序数组之间使用二分法,如果这道题只有一个有序数组,让我们求中位数的话,估计就是个 Easy 题。那么我们可以将两个有序数组混合起来成为一个有序数组再做吗,图样图森破,这个时间复杂度限制的就是告诉你金坷垃别想啦。那么我们还是要用二分法,而且是在两个数组之间使用,感觉很高端啊。那么回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,我们使用一个小 trick,我们分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。加入 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以2,还是其本身。
好,这里我们需要定义一个函数来在两个有序数组中找到第K个元素,下面重点来看如何实现找到第K个元素。首先,为了避免产生新的数组从而增加时间复杂度,我们使用两个变量i和j分别来标记数组 nums1 和 nums2 的起始位置。然后来处理一些 corner cases,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果 K=1 的话,那么我们只要比较 nums1 和 nums2 的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为我们需要在两个有序数组中找到第K个元素,为了加快搜索的速度,我们要使用二分法,那么对谁二分呢,数组么?其实要对K二分,意思是我们需要分别在 nums1 和 nums2 中查找第 K/2 个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第 K/2 个数字,所以我们需要先 check 一下,数组中到底存不存在第 K/2 个数字,如果存在就取出来,否则就赋值上一个整型最大值。如果某个数组没有第 K/2 个数字,那么我们就淘汰另一个数组的前 K/2 个数字即可。举个例子来说吧,比如 nums1 = {3},nums2 = {2, 4, 5, 6, 7},K=4,我们要找两个数组混合中第4个数字,那么我们分别在 nums1 和 nums2 中找第2个数字,我们发现 nums1 中只有一个数字,不存在第二个数字,那么 nums2 中的前2个数字可以直接跳过,为啥呢,因为我们要求整个混合数组的第4个数字,不管 nums1 中的那个数字是大是小,第4个数字绝不会出现在 nums2 的前两个数字中,所以可以直接跳过。
有没有可能两个数组都不存在第 K/2 个数字呢,这道题里是不可能的,因为我们的K不是任意给的,而是给的 m+n 的中间值,所以必定至少会有一个数组是存在第 K/2 个数字的。最后就是二分法的核心啦,比较这两个数组的第 K/2 小的数字 midVal1 和 midVal2 的大小,如果第一个数组的第 K/2 个数字小的话,那么说明我们要找的数字肯定不在 nums1 中的前 K/2 个数字,所以我们可以将其淘汰,将 nums1 的起始位置向后移动 K/2 个,并且此时的K也自减去 K/2,调用递归。反之,我们淘汰 nums2 中的前 K/2 个数字,并将 nums2 的起始位置向后移动 K/2 个,并且此时的K也自减去 K/2,调用递归即可,参见代码如下:
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//初始化第一个数组的中位数为left ,第二个数组的中位数为right
int m = nums1.length, n = nums2.length, left = (m + n + 1) / 2, right = (m + n + 2) / 2;
return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}
int findKth(int nums1[], int i, int nums2[], int j, int k) {
//利用 i和j标记这两个数组的起始位置,如果起始位置的长度都比数组长度大,直接返回另一个数组
if (i >= nums1.length) return nums2[j + k - 1];
if (j >= nums2.length) return nums1[i + k - 1];
//如果这两个数组里都只有一个元素,那么返回这两个数组中较小的哪一个
if (k == 1) return Math.min(nums1[i], nums2[j]);
//找出这两个数组的中位数
int mid1 = (i + k / 2 - 1) < nums1.length ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;
int mid2 = (i + k / 2 - 1) < nums2.length ? nums2[i + k / 2 - 1] : Integer.MAX_VALUE;
//对这两个数组的中位数进行比较后,如果谁的中位数小,那么要找的元素一定不再该数组的前半部分,直接
// 找他的后半部分,同时k的值也减少一半
if (mid1 < mid2) {
//进行递归查找
return findKth(nums1, i + k / 2, nums2, j, k-k/2);
} else {
return findKth(nums1, i, nums2, j + k / 2, k-k/2);
}
}
}
第五题
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
解题思路
回文串的方法就是两个两个的对称验证是否相等,那么对于找回文字串的问题,就要以每一个字符为中心,像两边扩散来寻找回文串,这个算法的时间复杂度是 O(n*n),可以通过 OJ,就是要注意奇偶情况,由于回文串的长度可奇可偶,比如 “bob” 是奇数形式的回文,“noon” 就是偶数形式的回文,两种形式的回文都要搜索,对于奇数形式的,我们就从遍历到的位置为中心,向两边进行扩散,对于偶数情况,我们就把当前位置和下一个位置当作偶数行回文的最中间两个字符,然后向两边进行搜索,参见代码如下:
//该问题还是可以归为窗口滑动问题,即维护了一个最大的回文窗口
//初始化回文左边界,初始化回文右边界
public String longestPalindrome1(String s) {
//进行判空操作 ,几乎所有字符题目必经之路
if (s == null || s.length() < 1) return "";
//首先定义一个最大值
String longest=s.substring(0,1);
for(int i=0;i<s.length();i++){
String tmp=helper(s,i,i);
//更新最大值
if(tmp.length()>longest.length()){
longest=tmp;
}
tmp=helper(s,i,i+1);
if(tmp.length()>longest.length()){
longest=tmp;
}
}
return longest;
}
//进行两端查找操作
public static String helper(String s, int begin, int end) {
while(begin>=0&&end<=s.length()-1&&s.charAt(begin)==s.charAt(end)){
begin--;
end++;
}
String SUB=s.substring(begin+1,end);
return SUB;
}
第六题
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入: s = “LEETCODEISHIRING”, numRows = 3
输出: “LCIRETOESIIGEDHN”
示例 2:
输入: s = “LEETCODEISHIRING”, numRows = 4
输出: “LDREOEIIECIHNTSG”
解释:
L D R
E O E I I
E C I H N
T S G
解题思路
观察发现,(默认在列上没有对齐的元素为红色元素)除了第一行和最后一行没有中间形成之字型的数字外,其他都有,而首位两行中相邻两个元素的index之差跟行数是相关的,为 2nRows - 2, 根据这个特点,我们可以按顺序找到所有的黑色元素在元字符串的位置,将他们按顺序加到新字符串里面。对于红色元素出现的位置也是有规律的,每个红色元素的位置为 j + 2nRows-2 - 2i, 其中,j为前一个黑色元素的列数,i为当前行数。 比如当n = 4中的那个红色5,它的位置为 1 + 24-2 - 2*1 = 5,为原字符串的正确位置。当我们知道所有黑色元素和红色元素位置的正确算法,我们就可以一次性的把它们按顺序都加到新的字符串里面。代码如下:
public String convert(String s, int nRows) {
//如果行数为1行直接返回本身
if (nRows <= 1) return s;
String res = "";
//根据规律找出下一个元素和他前一个元素的位置
int size = 2 * nRows - 2;
for (int i = 0; i < nRows; i++) {
for (int j = i; j < s.length(); j += size) {
res += s.charAt(j);
int tmp = j+ size - 2 * i;
if (i != 0 && i < nRows - 1 && tmp < s.length())
res += s.charAt(tmp);
}
}
return res;
}
第七题
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
解题思路
这道题主要关注点就是不越界就可以了!,主要就是简单的数学计算
123/10=12…3
12/10=1…2
1/10=0…1
代码如下:
public int reverse(int x) {
int res=0;
while(x!=0){
//对绝对值的判断保证正负都不会越界
if(Math.abs(res)>Integer.MAX_VALUE/10) {
return 0;
}
res=res*10+x%10;
x/=10;
}
return res;
}
第八题
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,qing返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
输入: “42”
输出: 42
示例 2:
输入: " -42"
输出: -42
解释: 第一个非空白字符为 ‘-’, 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:
输入: “4193 with words”
输出: 4193
解释: 转换截止于数字 ‘3’ ,因为它的下一个字符不为数字。
解题思路
-
若字符串开头是空格,则跳过所有空格,到第一个非空格字符,如果没有,则返回0.
-
若第一个非空格字符是符号+/-,则标记sign的真假,这道题还有个局限性,那就是在c++里面,±1和-+1都是认可的,都是-1,而在此题里,则会返回0.
-
若下一个字符不是数字,则返回0. 完全不考虑小数点和自然数的情况,不过这样也好,起码省事了不少。
-
如果下一个字符是数字,则转为整形存下来,若接下来再有非数字出现,则返回目前的结果。
-
还需要考虑边界问题,如果超过了整形数的范围,则用边界值替代当前值。
-
注意(s.charAt(i)-'0’的值就等于该值的本身,例如输入字符串"5",截取字符串处理后变成5(int类型))
-
INTERGER.MATH_VALUE/10 是因为下面base*10 , (str.charAt(i) - ‘0’) > 7) 是因为int的最大值为-2147483648~ 2147483647的原因
-
代码如下
public int myAtoi(String str) {
if (str.isEmpty()) return 0;
//初始化信号量sign 控制正负 base准备输出的值 i向后遍历的指针
int sign = 0, base = 0, i = 0, n = str.length();
while (i < n && str.charAt(i) == ' ') i++;
//判断正负并乘以相应的值
if (str.charAt(i) == '+' || str.charAt(i) == '-') {
sign = str.charAt(i) == '+' ? 1 : -1;
}
//进行越界判断
while (i < n && str.charAt(i) >= '0' && str.charAt(i) <= '9') {
if (base > Integer.MIN_VALUE || (base == Integer.MAX_VALUE && (str.charAt(i) - '0') > 7)) {
return (sign == 1) ? Integer.MIN_VALUE : Integer.MIN_VALUE;
}
//s.chatAt(i)-'0'的值就等于你截取的数字的真实值 类型也是int
base = base * 10 + str.charAt(i++) - '0';
}
return sign * base;
}
第九题
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
解题思路:
这题相对简单,思路同第7题
代码如下:
public boolean isPalindrome(int x) {
if(x==0){
return true;
}
if(x<0){
return false;
}
int res=0;
int tmp=x;
while(tmp!=0){
res=res*10+tmp%10;
tmp=tmp/10;
}
return res==x;
}
第十题
给定一个字符串 (s) 和一个字符模式 §。实现支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符。
‘*’ 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串 (s) ,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。
示例 2:
输入:
s = “aa”
p = “a*”
输出: true
解释: ‘*’ 代表可匹配零个或多个前面的元素, 即可以匹配 ‘a’ 。因此, 重复 ‘a’ 一次, 字符串可变为 “aa”。
示例 3:
输入:
s = “ab”
p = “."
输出: true
解释: ".” 表示可匹配零个或多个(’*’)任意字符(’.’)。
示例 4:
输入:
s = “aab”
p = “cab”
输出: true
解释: ‘c’ 可以不被重复, ‘a’ 可以被重复一次。因此可以匹配字符串 “aab”。
示例 5:
输入:
s = “mississippi”
p = “misisp*.”
输出: false
解题思路 :
主要要注意题干中给的字符匹配的特殊条件 题干中给出是 '.‘可以匹配任何字符 ,’*'可以匹配他前面字符的任意字符多个或者0个.也就是 如果你无论进行递归还是迭代到这样的特殊字符就要进行特殊处理.详情:若p为空,若s也为空,返回true,反之返回false。
-
若p的长度为1,若s长度也为1,且相同或是p为’.'则返回true,反之返回false。
-
若p的第二个字符不为*,若此时s为空返回false,否则判断首字符是否匹配,且从各自的第二个字符开始调用递归函数匹配。
-
若p的第二个字符为*,进行下列循环,条件是若s不为空且首字符匹配(包括p[0]为点),调用递归函数匹配s和去掉前两个字符的p(这样做的原因是假设此时的星号的作用是让前面的字符出现0次,验证是否匹配),若匹配返回true,否则s去掉首字母(因为此时首字母匹配了,我们可以去掉s的首字母,而p由于星号的作用,可以有任意个首字母,所以不需要去掉),继续进行循环。
-
返回调用递归函数匹配s和去掉前两个字符的p的结果(这么做的原因是处理星号无法匹配的内容,比如s=“ab”, p=“ab",直接进入while循环后,我们发现"ab"和"b"不匹配,所以s变成"b",那么此时跳出循环后,就到最后的return来比较"b"和"b"了,返回true。再举个例子,比如s="", p="a”,由于s为空,不会进入任何的if和while,只能到最后的return来比较了,返回true,正确)。
本题代码有3种解法,只要第一种懂,其他的就是你转换成动态规划的技巧的问题了.
//第一种方法递归查找
public boolean isMatch(String s, String p) {
if(p.isEmpty()){
return s.isEmpty();
}
//特殊配配当你截取的字符为*时p直接跳到下一个字符
if(p.length()>1&&p.charAt(1)=='*') {
return isMatch(s, p.substring(2)) || (!s.isEmpty()
//如果截取到'.'可以匹配 任意字符
&& (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.')
&& isMatch(s.substring(1), p));
}else {
return (!s.isEmpty()&&(s.charAt(0)==p.charAt(0)||p.charAt(0)=='.')&&
isMatch(s.substring(1),p.substring(1)));
}
}
//动态规划 自顶向下 (递归)
enum Result {
TRUE, FALSE
}
class Solution {
// 状态空间
Result[][] memo;
public boolean isMatch(String text, String pattern) {
memo = new Result[text.length() + 1][pattern.length() + 1];
return match(0, 0, text, pattern);
}
public boolean match(int i, int j, String text, String pattern) {
if (memo[i][j] != null) {
return memo[i][j] == Result.TRUE;
}
boolean ans;
if (j == pattern.length()){
ans = i == text.length();
} else{
boolean curMatch = (i < text.length() &&
(pattern.charAt(j) == text.charAt(i) ||
pattern.charAt(j) == '.'));
if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
ans = (match(i, j+2, text, pattern) ||
curMatch && match(i+1, j, text, pattern));
} else {
ans = curMatch && match(i+1, j+1, text, pattern);
}
}
memo[i][j] = ans ? Result.TRUE : Result.FALSE;
return ans;
}
}
//动态规划自底向上 迭代
class Solution1 {
public boolean isMatch(String s, String p) {
boolean[][] memo = new boolean[s.length() + 1][p.length() + 1];
memo[s.length()][p.length()] = true;
for (int i = s.length(); i >= 0; i--){
for (int j = p.length() - 1; j >= 0; j--){
boolean curMatch = (i < s.length() &&
(p.charAt(j) == s.charAt(i) ||
p.charAt(j) == '.'));
if (j + 1 < p.length() && p.charAt(j+1) == '*'){
memo[i][j] = memo[i][j+2] || curMatch && memo[i+1][j];
} else {
memo[i][j] = curMatch && memo[i+1][j+1];
}
}
}
return memo[0][0];
}
}
第十一题
给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
解题思路
本题属于简单题,我们只需要在数组的两端定义两个指针i,j然后让他们向中间移动,其实就是求一个长方形的面积,每次移动计算一个值,最后取值最大的那个(注意求高的时候要去最小的,否则构不成长方形)
public int maxArea(int[] height) {
//定义两个指针分别指向数组的左右两端
int res = 0, i = 0, j = height.length - 1;
//移动指针取面积最大的那个,但是要注意纵坐标要取最小的
res = Math.max(res, Math.min(height[i], height[j]) * (j - i));
if (height[i] < height[j]) {
i++;
} else {
j--;
}
return res;
}
第十二题
罗马数字包含以下七种字符: 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(1)
代码如下:
public String intToRoman(int num) {
String []M=new String []{"","M","MM","MMM"};
String []C={"","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"};
String []X={"","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"};
String []I={"","I","II","III","IV","V","VI","VII","VIII","IX"};
return M[num/1000]+C[(num%1000)/100]+X[(num)%100/10]+I[(num%10)];
}
第二种方法是这种题的常规解法,取商法取出每位上的数字,然后表示出来
public String intToRoman(int num) {
String res = "";
char[] roman={'M', 'D', 'C', 'L', 'X', 'V', 'I'};
char[] value={1000, 500, 100, 50, 10, 5, 1};
for (int n = 0; n < 7; n += 2) {
int x = num / value[n];
if (x < 4) {
for (int i = 1; i <= x; ++i) res += roman[n];
} else if (x == 4) {
res = res + roman[n] + roman[n - 1];
} else if (x > 4 && x < 9) {
res += roman[n - 1];
for (int i = 6; i <= x; ++i) res += roman[n];
} else if (x == 9) {
res = res + roman[n] + roman[n - 2];
}
num %= value[n];
}
return res;
}
第十三题
罗马数字包含以下七种字符: 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 的范围内。
示例 1:
输入: “III”
输出: 3
示例 2:
输入: “IV”
输出: 4
示例 3:
输入: “IX”
输出: 9
示例 4:
输入: “LVIII”
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: “MCMXCIV”
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
解题思路
1、基本数字Ⅰ、X 、C 中的任何一个,自身连用构成数目,或者放在大数的右边连用构成数目,都不能超过三个;放在大数的左边只能用一个。
2、不能把基本数字V 、L 、D 中的任何一个作为小数放在大数的左边采用相减的方法构成数目;放在大数的右边采用相加的方式构成数目,只能使用一个。
3、V 和X 左边的小数字只能用Ⅰ。
4、L 和C 左边的小数字只能用X。
5、D 和M 左边的小数字只能用C。
而这道题好就好在没有让我们来验证输入字符串是不是罗马数字,这样省掉不少功夫。我们需要用到HashMap数据结构,来将罗马数字的字母转化为对应的整数值,因为输入的一定是罗马数字,那么我们只要考虑两种情况即可:
第一,如果当前数字是最后一个数字,或者之后的数字比它小的话,则加上当前数字。
第二,其他情况则减去这个数字。
代码如下:
public int romanToInt(String s) {
int res=0;
Map<Character,Integer>m=new HashMap<Character, Integer>();
m.put('I',1);
m.put('V',5);
m.put('X',10);
m.put('L',50);
m.put('C',100);
m.put('D',500);
m.put('M',1000);
for(int i=0;i<s.length();i++){
int val=m.get(s.charAt(i));
//代码核心部分,如果当前取出的这个数为最后一个数,或者这个数大于他后面的数res++
//反之--
if(i==s.length()-1||m.get(s.charAt(i+1))<=m.get(s.charAt(i))){
res+=val;
}else {
res-=val;
}
}
return res;
}
第十四题
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:
输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z 。
解题思路
这道题让我们求一系列字符串的共同前缀,没有什么特别的技巧,无脑查找即可,我们定义两个变量i和j,其中i是遍历搜索字符串中的字符,j是遍历字符串集中的每个字符串。这里将单词上下排好,则相当于一个各行长度有可能不相等的二维数组,我们遍历顺序和一般的横向逐行遍历不同,而是采用纵向逐列遍历,在遍历的过程中,如果某一行没有了,说明其为最短的单词,因为共同前缀的长度不能长于最短单词,所以此时返回已经找出的共同前缀。我们每次取出第一个字符串的某一个位置的单词,然后遍历其他所有字符串的对应位置看是否相等,如果有不满足的直接返回res,如果都相同,则将当前字符存入结果,继续检查下一个位置的字符,参见代码如下:
public String longestCommonPrefix(String[] strs) {
if(strs==null||strs.length==0){
return "";
}
String res=new String();
//采用纵向遍历的方式
for(int j=0;j<strs[0].length();j++){
char c=strs[0].charAt(j);
for(int i=1;i<strs.length;i++){
if(j>=strs[i].length()||strs[i].charAt(j)!=c){
return res;
}
}
res+=Character.toString(c);
}
return res;
}
第十五题
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解题思路
我们对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是到倒数第三个就可以了。这里我们可以先做个剪枝优化,就是当遍历到正数的时候就break,为啥呢,因为我们的数组现在是有序的了,如果第一个要fix的数就是正数了,那么后面的数字就都是正数,就永远不会出现和为0的情况了。然后我们还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为我们不想把相同的数字fix两次。对于遍历到的数,用0减去这个fix的数得到一个target,然后只需要再之后找到两个数之和等于target即可。我们用两个指针分别指向fix数字之后开始的数组首尾两个数,如果两个数和正好为target,则将这两个数和fix的数一起存入结果中。然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果两数之和小于target,则我们将左边那个指针i右移一位,使得指向的数字增大一些。同理,如果两数之和大于target,则我们将右边那个指针j左移一位,使得指向的数字减小一些,代码如下:
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
if (i >= 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.
asList(nums[i], nums[left], nums[right]));
while (left + 1 < right && nums[left] == nums[left + 1]) {
left++;
}
while (right - 1 > left && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
} else if (sum > 0) {
right--;
} else {
left++;
}
}
}
return result;
}
第十六题
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
解题思路
这道题让我们求最接近给定值的三数之和,是在之前那道 3Sum 三数之和的基础上又增加了些许难度,那么这道题让我们返回这个最接近于给定值的值,即我们要保证当前三数和跟给定值之间的差的绝对值最小,所以我们需要定义一个变量diff用来记录差的绝对值,然后我们还是要先将数组排个序,然后开始遍历数组,思路跟那道三数之和很相似,都是先确定一个数,然后用两个指针left和right来滑动寻找另外两个数,每确定两个数,我们求出此三数之和,然后算和给定值的差的绝对值存在newDiff中,然后和diff比较并更新diff和结果closest即可,代码如下:
public int threeSumClosest(int[] nums, int target) {
int closet = nums[0] + nums[1] + nums[2];
int diff = Math.abs(closet - target);
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
int newDiff = Math.abs(sum - target);
if (diff > newDiff) {
diff = newDiff;
closet = sum;
}
if (sum<target) {
left++;
} else {
right--;
}
}
}
return closet;
}
第十七题
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
解题思路
我们用递归Recursion来解,我们需要建立一个字典,用来保存每个数字所代表的字符串,然后我们还需要一个变量level,记录当前生成的字符串的字符个数,实现套路和上述那些题十分类似。在递归函数中我们首先判断level,如果跟digits中数字的个数相等了,我们将当前的组合加入结果res中,然后返回。否则我们通过digits中的数字到dict中取出字符串,然后遍历这个取出的字符串,将每个字符都加到当前的组合后面,并调用递归函数即可,参见代码如下:
public List<String> letterCombinations(String digits) {
if(digits.isEmpty()){
return new ArrayList<>();
}
//建立收集元素的数组res
List<String>res=new ArrayList<>();
res.add("");
String dict[]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
//遍历你输入的字符
for(int i=0;i<digits.length();i++){
//建立临时数组t
List<String>t=new ArrayList<>();
//根据你输入字符串中的字符的真实值到上面的字典数中找到相应的位置 截取相应的字符
String str=dict[digits.charAt(i)-'0'];
//首先res是一个空的动态数组
// 例如你截取的对应的字符串为[a,b,c] [def]
for(int j=0;j<str.length();j++){
//遍历res,第一次遍历的时候res为空 临时数组t中加入 [a,b,c] ,第二次时动态数组
//变为[ad,ae,af,bd,be,bf,cd,ce,cf] 根据s+str.char.At(j) s.charAt(j)截取的是,你当前截取到的字符串
for(String s:res)t.add(s+str.charAt(j));
}
res=t;
}
return res;
}
第十八题
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
解题思路
和三数之和思路一样,只不过添加了一次for的遍历 .代码如下
public List<List<Integer>> fourSum(int[] nums, int target) {
//结果集
List<List<Integer>> result = new ArrayList();
//判空操作
if (nums == null || nums.length == 0) {
return result;
}
Arrays.sort(nums);
//求几个数的和就遍历到到他前一个数
for (int i = 0; i < nums.length - 3; i++) {
if (i > 0 && nums[i - 1] == nums[i]) {
continue;
}
for (int j = i + 1; j < nums.length - 2; j++) {
if (j > i + 1 && nums[j - 1] == nums[j]) {
continue;
}
//两个确定的指针
int left = j + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left + 1 < right && nums[left] == nums[left + 1]) {
left++;
}
while (right - 1 > left && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
} else if (sum > target) {
right--;
} else {
left++;
}
}
}
}
return result;
}
第十九题
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
解题思路
首先定义一个指针,向后查找,一直查找到链表的最后,确定链表的长度为len
,让len和n进行比较如果len==n,直接删除头结点即可,当n>len要删除的结点不存在,直接返回head即可,当n<len ,定义一个cut=len-n ,定义tmp=tmp.next向后查找到cut>1,为要删结点的前一个结点,直接进行指针重连即可.代码如下:
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode temp = head;
int len = 0;
while (temp!= null) {
temp = temp.next;
len++;
}
if (n == len) {
return head.next;
}
temp = head;
int cut = len - n;
//判断指针到达所找元素的前一个位置,进行指针重连即可
while (cut-- > 1) {
temp = temp.next;
}
if (temp.next != null) {
temp.next = temp.next.next;
return head;
}
return null;
}
第二十题
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
解题思路
首先我们定义一个栈 ,然后进行遍历s字符串,如果遍历到的元素等于{,[,(,将他们进行压栈,否则判断栈是否为空,如果不为空返回false,然后继续进行截取,如果截取到的元素为],},),进行取栈顶元素的操作,如果取得的栈顶元素为对应的括号则返回true,反之为false.最后弹出所有元素,进行判空.
代码如下:
Stack<Character> stack=new Stack<Character>();
for(int i=0;i<s.length();i++){
if (s.charAt(i) == '(' || s.charAt(i) == '[' || s.charAt(i) =='{') {
stack.push(s.charAt(i));
}else {
if (stack.isEmpty())
return false;
if(s.charAt(i)==')'&&stack.peek()!='(')
return false;
if(s.charAt(i)==']'&&stack.peek()!='[')
return false;
if(s.charAt(i)=='}'&&stack.peek()!='{')
return false;
stack.pop();
}
}
return stack.isEmpty();
}
第二十一题
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
解题思路
道混合插入有序链表和我之前那篇混合插入有序数组非常的相似Merge Sorted Array,仅仅是数据结构由数组换成了链表而已,代码写起来反而更简洁。具体思想就是新建一个链表,然后比较两个链表中的元素值,把较小的那个链到新链表中,由于两个输入链表的长度可能不同,所以最终会有一个链表先完成插入所有元素,则直接另一个未完成的链表直接链入新链表的末尾。代码如下:
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy=new ListNode(-1);
ListNode cur=dummy;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
cur.next=l1;
l1=l1.next;
}else {
cur.next=l2;
l2=l2.next;
}
cur=cur.next;
}
cur.next=l1!=null?l1:l2;
return dummy.next;
}
第二十二题
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
解题思路
最长有效括号,这道题给定一个数字n,让生成共有n个括号的所有正确的形式,对于这种列出所有结果的题首先还是考虑用递归Recursion来解,由于字符串只有左括号和右括号两种字符,而且最终结果必定是左括号3个,右括号3个,所以我们定义两个变量left和right分别表示剩余左右括号的个数,如果在某次递归时,左括号的个数大于右括号的个数,说明此时生成的字符串中右括号的个数大于左括号的个数,即会出现’)('这样的非法串,所以这种情况直接返回,不继续处理。如果left和right都为0,则说明此时生成的字符串已有3个左括号和3个右括号,且字符串合法,则存入结果中后返回。如果以上两种情况都不满足,若此时left大于0,则调用递归函数,注意参数的更新,若right大于0,则调用递归函数,同样要更新参数。代码如下:
public List<String> generateParenthesis(int n) {
List<String>res=new ArrayList<String>();
helper(n,n,"",res);
return res;
}
void helper(int left,int right,String out,List<String>res){
if(left<0||right<0||left>right){
return;
}
if(left==0&&right==0){
res.add(out);
return;
}
helper(left-1,right,out+"(",res);
helper(left,right-1,out+")",res);
}
第二十三题
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
解题思路
这种解法利用了最小堆这种数据结构,我们首先把k个链表的首元素都加入最小堆中,它们会自动排好序。然后我们每次取出最小的那个元素加入我们最终结果的链表中,然后把取出元素的下一个元素再加入堆中,下次仍从堆中取出最小的元素做相同的操作,以此类推,直到堆中没有元素了,此时k个链表也合并为了一个链表,返回首节点即可,代码如下:
public ListNode mergeKLists(ListNode[] lists){
PriorityQueue <ListNode>heap=new PriorityQueue(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val-o2.val;
}
});
for(ListNode node:lists){
if(node!=null){
//这里加的都是各个链表的头结点
heap.offer(node);
}
}
ListNode pre=new ListNode(-1);
ListNode temp=pre;
while(!heap.isEmpty()){
ListNode curr=heap.poll();
temp.next=new ListNode(curr.val);
if(curr.next!=null){
heap.offer(curr.next);
}
temp=temp.next;
}
return pre.next;
}
第二十四题
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
解题思路
利用回溯算法的思想,递归遍历到末尾.然后先交换末尾两个,然后依次交换.
代码如下:
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null)return head;
ListNode t=head.next;
head.next=swapPairs(head.next.next);
t.next=head;
return t;
}
第二十五题
给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明 :
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
解题思路:
这道题让我们以每k个为一组来翻转链表,实际上是把原链表分成若干小段,然后分别对其进行翻转,那么肯定总共需要两个函数,一个是用来分段的,一个是用来翻转的,我们就以题目中给的例子来看,对于给定链表1->2->3->4->5,一般在处理链表问题时,我们大多时候都会在开头再加一个dummy node,因为翻转链表时头结点可能会变化,为了记录当前最新的头结点的位置而引入的dummy node,那么我们加入dummy node后的链表变为-1->1->2->3->4->5,如果k为3的话,我们的目标是将1,2,3翻转一下,那么我们需要一些指针,pre和next分别指向要翻转的链表的前后的位置,然后翻转后pre的位置更新到如下新的位置:
以此类推,只要cur走过k个节点,那么next就是cur->next,就可以调用翻转函数来进行局部翻转了,注意翻转之后新的cur和pre的位置都不同了,那么翻转之后,cur应该更新为pre->next,而如果不需要翻转的话,cur更新为cur->next,代码如下所示:
public ListNode reverseKGroup(ListNode head, int k) {
if(head==null||k==1)return head;
ListNode dummy=new ListNode(-1),pre=dummy,cur=head;
dummy.next=head;
for(int i=1;cur!=null;i++){
if(i%k==0){
pre=reverseOneGroup(pre,cur.next);
cur=pre.next;
}else {
cur=cur.next;
}
}
return dummy.next;
}
ListNode reverseOneGroup(ListNode pre,ListNode next){
ListNode last=pre.next,cur=last.next;
while(cur!=next){
last.next=cur.next;
cur.next=pre.next;
pre.next=cur;
cur=last.next;
}
return last;
}
第二十六题
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
解题思路:
定义快慢指针,快指针开始指向第2个元素的位置,慢指针指向第一个元素的位置,
首先让快指针移动如果nums[i]!=nums[j];i++ 然后把nums[j]的值赋给nums[i]然后快指针继续移动,最后返回慢指针指向的位置加1 ,代码如下:
public static int removeDuplicates(int[] nums) {
if(nums.length==0||nums==null)return 0;
int i=0;
for(int j=1;j<nums.length;j++){
if(nums[i]!=nums[j]){
i++;
nums[i]=nums[j];
}
}
System.out.println(i+1);
return i+1;
}
第二十七题
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
解题思路
这道题让我们移除一个数组中和给定值相同的数字,并返回新的数组的长度。是一道比较容易的题,我们只需要一个变量用来计数,然后遍历原数组,如果当前的值和给定值不同,我们就把当前值覆盖计数变量的位置,并将计数变量加1。代码如下:
public static int removeElement(int[] nums, int val) {
int i=0;
for(int j=0;j<nums.length;j++){
if(nums[j]!=val) nums[i++]=nums[j];
}
System.out.println(i);
return i;
}
第二十八题
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = “hello”, needle = “ll”
输出: 2
示例 2:
输入: haystack = “aaaaa”, needle = “bba”
输出: -1
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
解题思路
这道题让我们在一个字符串中找另一个字符串第一次出现的位置,那我们首先要做一些判断,如果子字符串为空,则返回0,如果子字符串长度大于母字符串长度,则返回-1。然后我们开始遍历母字符串,我们并不需要遍历整个母字符串,而是遍历到剩下的长度和子字符串相等的位置即可,这样可以提高运算效率。然后对于每一个字符,我们都遍历一遍子字符串,一个一个字符的对应比较,如果对应位置有不等的,则跳出循环,如果一直都没有跳出循环,则说明子字符串出现了,则返回起始位置即可,代码如下:
public static int strStr(String haystack, String needle) {
if(haystack==null||needle==null||haystack.length()<needle.length()){
return -1;
}
for(int i=0;i<=haystack.length()-needle.length();i++){
if(haystack.substring(i,i+needle.length()).equals(needle)){
System.out.println(i);
return i;
}
}
return -1;
}
第二十九题
* 解题思路:这题是除法,所以先普及下除法术语
* 商,公式是:(被除数-余数)÷除数=商,记作:被除数÷除数=商...余数,是一种数学术语。
* 在一个除法算式里,被除数、余数、除数和商的关系为:(被除数-余数)÷除数=商,记作:被除数÷除数=商...余数,
* 进而推导得出:商×除数+余数=被除数。
*
* 要求商,我们首先想到的是减法,能被减多少次,那么商就为多少,但是明显减法的效率太低
*
* 那么我们可以用位移法,因为计算机在做位移时效率特别高,向左移1相当于乘以2,向右位移1相当于除以2
*
* 我们可以把一个dividend(被除数)先除以2^n,n最初为31,不断减小n去试探,当某个n满足dividend/2^n>=divisor时,
*
* 表示我们找到了一个足够大的数,这个数*divisor是不大于dividend的,所以我们就可以减去2^n个divisor,以此类推
*
* 我们可以以100/3为例
*
* 2^n是1,2,4,8...2^31这种数,当n为31时,这个数特别大,100/2^n是一个很小的数,肯定是小于3的,所以循环下来,
*
* 当n=5时,100/32=3, 刚好是大于等于3的,这时我们将100-32*3=4,也就是减去了32个3,接下来我们再处理4,同样手法可以再减去一个3
*
* 所以一共是减去了33个3,所以商就是33
*
* 这其中得处理一些特殊的数,比如divisor是不能为0的,Integer.MIN_VALUE和Integer.MAX_VALUE
***
public int divide(int dividend, int divisor) {
if (dividend == 0) {
return 0;
}
if (dividend == Integer.MIN_VALUE && divisor == -1) {
return Integer.MAX_VALUE;
}
boolean negative;
negative = (dividend ^ divisor) <0;//用异或来计算是否符号相异
long t = Math.abs((long) dividend);
long d= Math.abs((long) divisor);
int result = 0;
for (int i=31; i>=0;i--) {
if ((t>>i)>=d) {
result+=1<<i;
t-=d<<i;
}
}
return negative ? -result : result;
}
第三十题
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
解题思路
利用集合的填充和移除方法
List<Integer> answer=new ArrayList<>();
int m=words.length;
if (m == 0 || s.length() == 0) {
return answer;
}
int n=words[0].length();
for(int i=0;i<=s.length()-m*n;i++){
String sub=s.substring(i,i+m*n);
List list=new ArrayList();
for(int k=0;k<m;k++)
list.add(sub.substring(k*n,(k+1)*n));
for(int j=0;j<m;j++) {
if (list.contains(words[j])) {
list.remove(words[j]);
} else {
break;
}
}
if (list.isEmpty()) {
answer.add(i);
}
}
System.out.println(answer);
return answer;
}
第三十一题
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
解题思路
其实就是从数组倒着查找,找到nums[i] 比nums[i+1]小的时候,就将nums[i]跟nums[i+1]到nums[nums.length - 1]当中找到一个最小的比nums[i]大的元素交换。交换后,再把nums[i+1]到nums[nums.length-1]排序.
public void nextPermutation(int[] nums) {
int n=nums.length-2;
while(n>=0&&nums[n]>=nums[n+1]){
n--;
}
if(n>=0){
int m=nums.length-1;
while(m>=0&&nums[n]>=nums[m]){
m--;
}
swap(nums,n,m);
}
reverse(nums,n+1);
}
public void swap(int nums[],int i, int j){
int tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
}
public void reverse(int nums[],int start){
int end=nums.length-1;
while(start<=end) {
int tmp = nums[start];
nums[start++] = nums[end];
nums[end--] = tmp;
}
}
第三十二题
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”
示例 2:
输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”
解题思路
思路很简单,优化检测括号是否配对的算法,栈的数据类型使用元组,即检测完后检查栈中元素,求出各相邻元素index差值的最大值即可。
public int longestValidParentheses(String s) {
int result = 0;
Stack<Integer> stack = new Stack();
stack.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
stack.pop();
if (stack.isEmpty()) {
stack.push(i);
} else {
result = Math.max(result, i - stack.peek());
}
}
}
return result;
}
第三十三题
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
解题思路
本题主要使用二分查找,定义最小下标和最大的下标,然后求出mid,如果nums[mid]==target,就返回,如果查找不到,就返回-1,如果nums[mid] < target 就先从mid + 1, 到high查找,否则从low, mid - 1先进行查找.直到查找到结果返回
代码如下:
public static int search(int[] nums, int target) {
System.out.println(search(nums, 0, nums.length - 1, target));
return search(nums, 0, nums.length - 1, target);
}
private static int search(int[] nums, int low, int high, int target) {
if (low > high)
return -1;
int mid = (low + high) / 2;
if (nums[mid] == target)
return mid;
if (nums[mid] < nums[high]) {
if (nums[mid] < target && target <= nums[high])
return search(nums, mid + 1, high, target);
else
return search(nums, low, mid - 1, target);
} else {
if (nums[low] <= target && target < nums[mid])
return search(nums, low, mid - 1, target);
else
return search(nums, mid + 1, high, target);
}
}
第三十四题
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
解题思路:
本题主要使用二分查找,定义最小下标和最大的下标,然后求出mid,如果nums[mid]==target,就返回,如果查找不到,就返回-1,如果nums[mid] < target 就先从mid + 1, 到high查找,否则从low, mid - 1先进行查找.直到查找到结果返回定义为idx,然后分别在数组中向左或者向右进行查找.找到相应下标返回.
public static int[] searchRange(int[] nums, int target) {
int idx =search(nums,0,nums.length-1,target);
if(idx==-1)return new int []{-1,-1};
int left=idx,right=idx;
//向左查找 记录最左边等于target的下标
while(left>0&&nums[left-1]==nums[idx])left--;
//向右查找 记录最右边等于target的下标
while (right<=nums.length-1&&nums[right+1]==nums[idx])right++;
System.out.println(left+"+"+right);
return new int []{left,right};
}
public static int search( int nums[],int low,int high,int target){
if(high<low)return -1;
int mid=(low+high)/2;
if(nums[mid]==target)return mid;
if(nums[mid]<nums[high]){
if(nums[mid]<target&&target<=nums[high]){
return search(nums,mid+1,high,target);
}else {
return search(nums,low,mid-1,target);
}
}else if(target<nums[mid]&&target>=nums[low]){
return search(nums,low,mid-1,target);
}else {
return search(nums,mid+1,high,target);
}
}
第三十五题
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
解题思路
直接用nums[i]和target比较如果nums[i]>=target 返回i,否则返回数组长度!
public int searchInsert(int[] nums, int target) {
for(int i = 0; i < nums.length;i++){
if(nums[i] >= target){
return i;
}
}
return nums.length;
}
第三十六题
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
解题思路
每一个小九宫格(互不交叉,总共九个小九宫格)必须是数字1~9且不重复
依次检查每行,每列,每个子九宫格是否出现重复元素,如果出现返回false,否则返回true.
难点在于表示第i个九宫格每个格点的坐标。
观察行号规律:
第0个九宫格:000111222; 第1个九宫格:000111222; 第2个九宫格:000111222;
第3个九宫格:333444555; 第4个九宫格:333444555; 第5个九宫格:333444555;
第6个九宫格:666777888; 第7个九宫格:666777888; 第8个九宫格:666777888;
可见对于每三个九宫格行号增3;对于单个九宫格,每三个格点行号增1。
因此第i个九宫格的第j个格点的行号可表示为i/3*3+j/3(每个小九宫格j都是从0~9递增)
观察列号规律:
第0个九宫格:012012012; 第1个九宫格:345345345; 第2个九宫格:678678678;
第3个九宫格:012012012; 第4个九宫格:345345345; 第5个九宫格:678678678;
第6个九宫格:012012012; 第7个九宫格:345345345; 第8个九宫格:678678678;
可见对于下个九宫格列号增3,循环周期为3;对于单个九宫格,每个格点行号增1,周期也为3。
周期的数学表示就是取模运算mod。
因此第i个九宫格的第j个格点的列号可表示为i%3*3+j%3(每个小九宫格j都是从0~9递增)
部分填充的有效数独,不需要填充
public boolean isValidSudoku(char[][] board) {
boolean [][]row=new boolean[9][9];
boolean [][]col=new boolean[9][9];
boolean [][]block=new boolean[9][9];
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]!='.'){
int num=board[i][j]-'1';
int blockindex=i/3*3+j/3;
if(row[i][num]||col[j][num]||block[blockindex][num]){
return false;
}else {
row[i][num]=true;
col[j][num]=true;
block[blockindex][num]=true;
}
}
}
}
return true;
}
第三十七题
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示。
解题思路
这道题和是上一道题的延伸,需要在上一题的基础上加上dfs深搜和回溯,现在数独的格子中寻找空格,num设置的值为1到9,遍历num如果都符合数独的原则,那么把当前遍历到的数添加到数独中,如果不符合,进行回溯,把遍历到的位置所有的值设置为false,并且把当前的值设置为空.最后填满整个数独.
public void solveSudoku(char[][] board) {
/**
* 记录某行,某位数字是否已经被摆放
*/
boolean[][] row = new boolean[9][10];
/**
* 记录某列,某位数字是否已经被摆放
*/
boolean[][] col = new boolean[9][10];
/**
* 记录某 3x3 宫格内,某位数字是否已经被摆放
*/
boolean[][] block = new boolean[9][10];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int num = board[i][j] - '0';
row[i][num] = true;
col[j][num] = true;
// blockIndex = i / 3 * 3 + j / 3,取整
block[i / 3 * 3 + j / 3][num] = true;
}
}
}
dfs(board, row, col, block, 0, 0);
}
private boolean dfs(char[][] board, boolean[][] row, boolean[][] col, boolean[][] block, int i, int j) {
// 找寻空位置
while (board[i][j] != '.') {
if (++j >= 9) {
i++;
j = 0;
}
if (i >= 9) {
return true;
}
}
//在空白位置添加數字,添加前验证 ,符合的话添加进去
for (int num = 1; num <= 9; num++) {
int blockIndex = i / 3 * 3 + j / 3;
if (!row[i][num] && !col[j][num] && !block[blockIndex][num]) {
// 递归
board[i][j] = (char) ('0' + num);
row[i][num] = true;
col[j][num] = true;
block[blockIndex][num] = true;
if (dfs(board, row, col, block, i, j)) {
return true;
} else {
// 回溯
row[i][num] = false;
col[j][num] = false;
block[blockIndex][num] = false;
board[i][j] = '.';
}
}
}
return false;
}
第三十八题
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
-
1
-
11
-
21
-
1211
-
111221
1 被读作 “one 1” (“一个一”) , 即 11。
11 被读作 “two 1s” (“两个一”), 即 21。
21 被读作 “one 2”, “one 1” (“一个二” , “一个一”) , 即 1211。
解题思路
该题可以理解为几个他,或者一个他 ,然后进行判断,如果符合的话进入相应循环对字符串进行扩充
public String countAndSay1(int n) {
StringBuilder cuur=new StringBuilder("1");
StringBuilder prev;
int count;
char say;
for(int i=1;i<n;i++){
prev=cuur;
cuur=new StringBuilder();
count=1;
say=prev.charAt(0);//截取为1的
for (int j=1,len=prev.length();j<len;j++){
//加入截取的数字不为1
if(prev.charAt(j)!=say) {
//一个他
cuur.append(count).append(say);
count = 1;
//say更新为你要查找的那个数
say = prev.charAt(j);
}else {
count++;
}
}
//几个他
cuur.append(count).append(say);
}
return cuur.toString();
}
第三十九题
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
解题思路
都是需要另写一个递归函数,这里我们新加入三个变量,start记录当前的递归到的下标,out为一个解,res保存所有已经得到的解,每次调用新的递归函数时,此时的target要减去当前数组的的数,具体看代码如下
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> listAll=new ArrayList<List<Integer>>();
List<Integer> list=new ArrayList<Integer>();
//排序
Arrays.sort(candidates);
find(listAll,list,candidates,target,0);
System.out.println(listAll);
return listAll;
}
public static void find(List<List<Integer>> listAll,List<Integer> tmp,int[] candidates, int target,int num){
//递归的终点
if(target==0){
listAll.add(tmp);
return;
}
if(target<candidates[0]) return;
for(int i=num;i<candidates.length&&candidates[i]<=target;i++){
//深拷贝
List<Integer> list=new ArrayList<>(tmp);
list.add(candidates[i]);
//递归运算,将i传递至下一次运算是为了避免结果重复。
find(listAll,list,candidates,target-candidates[i],i);
}
}
第四十道题
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
解题思路
主要运用递归查找,与上一题的不同之处在于这次,不允许出现重复元素的情况,那递归的过程中就要判断如果num[i]==num[i-1]的话跳出本次循环,进入下次循环,同时i的值也要变为i+1;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> listAll=new ArrayList<List<Integer>>();
List<Integer> list=new ArrayList<Integer>();
//排序
Arrays.sort(candidates);
findAll(candidates,target,0,listAll,list);
// System.out.println(listAll);
return listAll;
}
public void findAll(int []candidates,int target,int start,List<List<Integer>> listAll,List <Integer> temp){
if(target==0){
listAll.add(temp);
return;
}
if(candidates[0]>target)return;
for(int i=start;i<candidates.length&&candidates[i]<=target;i++){
if(i>start&&candidates[i]==candidates[i-1])
continue;
List list=new ArrayList(temp);
list.add(candidates[i]);
findAll(candidates,target-candidates[i],i+1,listAll,list);
}
}
第四十一题
给定一个未排序的整数数组,找出其中没有出现的最小的正整数。
示例 1:
输入: [1,2,0]
输出: 3
示例 2:
输入: [3,4,-1,1]
输出:2
解题思路
遍历一次数组把大于等于1的和小于数组大小的值放到原数组对应位置,nums[nums [i]-1]=nums[i],因为数组中有0下标,所以下标对应的时候要减一,在遍历一遍数组,如果nums[i]!=i+1,直接返回i+1就可以了,否则没有查找到,直接返回数组的长度加1即可
public int firstMissingPositive(int[] nums) {
for(int i=0;i<nums.length;i++){
while(nums[i]>=1&&nums[i]<=nums.length&&nums[nums[i]-1]!=nums[i]){
int temp=nums[nums[i]-1];
nums[nums[i]-1]=nums[i];
nums[i]=temp;
}
}
for (int i=0;i<nums.length;i++)
if(nums[i]!=i+1)
return i+1;
return nums.length+1;
}
第四十二题
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解题思路
设计一个单调栈,单调栈是什么,可以定义单调栈的规则,首先把你遍历到的下标加入到栈中,当你遍历到的元素比栈顶的元素大或者小(根据自己定义的规则)进行弹栈的操作,本题是 如果你遍历到的元素比栈顶的元素大则进行弹栈的操作,这也就保证了当你遍历到的元素比和他相邻的元素大的时候进行相应的结算!从而计算出雨水的面积
public int trap(int[] height) {
if(height==null||height.length==0)return 0;
int res=0;
Stack <Integer>stack=new Stack();
for(int i=0;i<height.length;i++){
//当栈非空,并且当前元素比栈顶的元素大,进行弹栈结算操作
while(!stack.isEmpty()&& height[i]>height[stack.peek()]){
int temp=stack.pop();
//如果弹栈后栈为空了,直接跳出循环,说明已经计算结束
if(stack.isEmpty())break;
//长方形计算高度
res+=(Math.min(height[i],height[stack.peek()])-height[temp])
//下标相减计算长方形宽度
*(i-stack.peek()-1);
}
//向栈中压入下标,这是第一步做的
stack.push(i);
}
return res;
}
第四十三题
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = “2”, num2 = “3”
输出: “6”
示例 2:
输入: num1 = “123”, num2 = “456”
输出: “56088”
解题思路
首先本题 不能进行直接运算(Python3除外),要一位一位的算,然后进位,这样计算,
首先定义一个结果数组muk用保存最后算出来的值,长度为num1的长度+num2的长度+2;然后遍历数组num1和num2,从最后开始遍历,因为要向前遍历,然后相乘的结果(num1.charAt(i)-‘0’)*((nums.charAt(j)-’ 0’)用mul记录一下,最后mul/10保存在muk[i+j]里面 muk[i+j]+;
mul%10 存在muk[i+j+1]里面 最后遍历去’0’,输出
int n1=num1.length()-1;
int n2=num2.length()-1;
if(n1<0||n2<0) return "";
int []muk=new int [n1+n2+2];
for(int i=n1;i>=0;i--){
for(int j=n2;j>=0;j--){
int mul=(num1.charAt(i)-'0')*(num2.charAt(j)-'0');
mul+=muk[i+j+1];
muk[i+j]+=mul/10;
muk[i+j+1]=mul%10;
}
}
StringBuilder sb=new StringBuilder();
int i=0;
while(i<muk.length-1&&muk[i]==0)
i++;
for(;i<muk.length;i++)
sb.append(muk[i]);
return sb.toString();
第四十四题
给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。
‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符
public boolean isMatch(String s, String p) {
boolean[][] m = new boolean[s.length()+1][p.length()+1];
m[0][0] = true; //两个字符串都为空是肯定匹配
for(int i = 0; i <= s.length(); i++) {
for(int j = 1; j <= p.length(); j++) {
if(p.charAt(j-1) == '*') {
//m[i][j-1] 即当前'*'匹配一个空字符
//m[i-1][j] 即当前'*'匹配一个字符s.charAt(i-1)
//这里之所以不是m[i-1][j-1]是因为当前'*'可能在前面一匹配了若干个字符
m[i][j] = m[i][j-1] || (i > 0 && m[i-1][j]);
} else {
//前面的m(0~i-2)和p(0~j-2)能匹配且当前字符能匹配
m[i][j] = i > 0 && m[i-1][j-1] && (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '?');
}
}
}
return m[s.length()][p.length()];
}
第四十五题
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
解题思路
首先定义一个step,要走的步数,定义一个reach,表示下一次要到达的地方,定义一个nextreachnums[i]+i,表示能到达的最远的地方,如果 ireach,进行步数的更新,如果,最大能够走的步数大于数组的长度,进行返回step+1;
public int jump(int[] nums) {
if(nums.length==1)return 0 ;
int reach=0;
int nextreach=nums[0];
int step=0;
for(int i=0;i<nums.length;i++){
nextreach=Math.max(i+nums[i],nextreach);
if(nextreach>=nums.length-1)return (step+1);
if(i==reach){
step++;
reach=nextreach;
}
}
return step;
}
第四十六题
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
解题思路
交换,深搜,交换,start==end,加入到集合中
public static List<List<Integer>> permute(int[] nums) {
List<List<Integer>>list=new ArrayList();
return DFSHelper(list,nums,0,nums.length-1);
}
public static List<List<Integer>> DFSHelper(List<List<Integer>>list,int nums[],int start,int end){
List<Integer>temp=new ArrayList();
//设置递归终点
if(start==end){
for(int i=0;i<=end;i++){
temp.add(nums[i]);
}
list.add(temp);
return list;
}else{
//本次递归应该 做什么
for(int j=start;j<=end;j++){
swap(nums,start,j);
DFSHelper(list,nums,start+1,end);
//交换回来 ,免得出现重复情况
swap(nums,start,j);
}
//本次递归应该返回什么
return list;
}
}
public static void swap(int nums[],int i,int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
第四十七题
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
解题思路
加一个去重
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
dfs(nums,0);
return ans;
}
private void dfs(int[] nums,int cur) {
if (cur == nums.length) {
List<Integer> line = new ArrayList<>();
for (int i : nums) {
line.add(i);
}
ans.add(line);
} else {
for (int i = cur;i < nums.length;i++) {
if (canSwap(nums,cur,i)) {
swap(nums,cur,i);
dfs(nums,cur + 1);
swap(nums,cur,i);
}
}
}
}
private boolean canSwap(int nums[],int begin,int end) {
for (int i = begin;i < end;i++) {
if (nums[i] == nums[end]) {
return false;
}
}
return true;
}
private void swap(int nums[],int i,int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
第四十八题
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
解题思路
本题是一道宏观调度题,只要关注4个点是如何变化的,在用变量控制范围,
首点为(start,start),末尾点为(end,end);
public void rotate(int[][] matrix) {
int len=matrix.length;
for(int i=0;i<len;i++){
int start =i;
int end=len-i-1;
for(int j=i;j<end-start;j++){
int temp= matrix[start+j][start];
matrix[start][start+j]=matrix[end-j][start];
matrix[end-j][start]=matrix[end][end-j];
matrix[end][end-j]=matrix[start+j][end];
matrix[start+j][end]=temp;
}
}
}
第四十九题
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
解题思路
先遍历一下String类型的数组,然后把他转换成char类型的数组进行排序,然后将排序过后的数组和未排序的数组分别装进 hashmap中,然后根据key,取values,取出的就是答案
Map<String,List<String>>map=new HashMap<String,List<String>>();
char[] chars=new char[strs.length];
for(String str:strs){
chars = str.toCharArray();
Arrays.sort(chars);
//computeIfAbsent用法如果从map中根据key获取value,value对应的值为空,则会将第二个参数的返回值存入并返回,
map.computeIfAbsent(new String (chars), x -> new ArrayList<>()).add(str);
}
return new ArrayList<List<String>>(map.values());
}
第五十题
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
解题思路
使用折半计算,每次把n缩小一半,这样n最终会缩小到0,任何数的0次方都为1,这时候我们再往回乘,如果此时n是偶数,直接把上次递归得到的值算个平方返回即可,如果是奇数,则还需要乘上个x的值。最后根据n的正负看返回res还是1/res
public double myPow(double x, int n) {
double res = 1.0;
for(int i = n; i != 0; i /= 2){
if(i % 2 != 0){
res*= x;
}
x *= x;
}
return n < 0 ? 1 / res : res;
}
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解题思路
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
二维矩阵存储棋盘状态, 采用递归解法
public List<List<String>> solveNQueens(int n) {
List<List<String>> list = new ArrayList<>();
int[] arr = new int[n];
putQueen(list, 0, n, arr);
return list;
}
// 尝试放置:递归+回溯
public void putQueen(List<List<String>> list,int row,int n,int[] arr){
if(row==n){
List<String> temp = new ArrayList<>();
for(int i=0;i<n;i++){
StringBuilder sb = new StringBuilder();
for(int j=0;j<n;j++){
if(arr[i]==j) sb.append("Q");
else sb.append(".");
}
temp.add(sb.toString());
}
list.add(temp);
return;
}
for(int i=0;i<n;i++){
//用横坐标表示纵坐标
arr[row] = i;
if(check(arr,row)){
putQueen(list,row+1,n,arr);
}
}
}
// 检测放置的皇后是否有问题
public boolean check(int[] arr,int row) {
for(int i=0;i<row;i++){
if(arr[i]==arr[row]||Math.abs(row-i)==Math.abs(arr[row]-arr[i]))
return false;
}
return true;
}
第五十一题
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给定一个整数 n,返回 n 皇后不同的解决方案的数量。
示例:
输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
[
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
public int totalNQueens(int n) {
if(n<1)return 0;
int record[]=new int [n];
return process(0,record,n);
}
public static int process(int i,int [] record,int n){
if(i==n)return 1;
int res=0;
for(int j=0;j<n;j++){
if(isValid(record,i,j)){
record[i]=j;
res+=process(i+1,record,n);
}
}
return res;
}
public static boolean isValid(int record[],int i,int j){
for(int k=0;k<i;k++){
//横纵坐标不等 ,并且对角线不等
if(record[k]==j||Math.abs(record[k]-j)==Math.abs(i-k)){
return false;
}
}
return true;
}