10.28 LeetCode周总结
1、两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
(1)此题首先想到的就是暴力法,时间复杂度是O(n^2)
(2)更巧妙的是用map,利用键值对,只需O(n)
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<nums.length;i++){
int a=target-nums[i];
//判断是否存在此键
if(map.containsKey(a)){
//若存在,则返回键的值,和i
return new int[]{map.get(a),i};
}
map.put(nums[i],i);
}
System.out.println("No");
return new int[]{-1,-1};
}
}
2、两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
循环实现:
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
int a=0;
struct ListNode *head,*cur,*next;
head=(struct ListNode *)malloc(sizeof(struct ListNode));
head->next=NULL;
cur=head;
while(l1||l2||a){
next=(struct ListNode *)malloc(sizeof(struct ListNode));
next->next=NULL;
cur->next=next;
cur=next;
l1!=NULL?(a+=l1->val,l1=l1->next):(a+=0);
l2!=NULL?(a+=l2->val,l2=l2->next):(a+=0);
int b=a%10;
next->val=b;
a=a/10;
}
return head->next;
}
递归实现:
int c=0;
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
if(l1==NULL&&l2==NULL&&c==0)
return NULL;
l1!=NULL?(c+=l1->val,l1=l1->next):(c+=0);
l2!=NULL?(c+=l2->val,l2=l2->next):(c+=0);
struct ListNode *p=(struct ListNode *)malloc(sizeof(struct ListNode));
int a=c%10;
c=c/10;
p->val=a;
p->next=addTwoNumbers(l1,l2);
return p;
}
3.无重复字符的最长子串长度
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
(1)暴力法:
(2)滑动窗口
通过使用 HashSet 作为滑动窗口,我们可以用 O(1)的时间来完成对字符是否在当前的子字符串中的检查。
滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [i,j)左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [i,j) 向右滑动 1 个元素,则它将变为 [i+1,j+1)(左闭,右开)。
回到我们的问题,我们使用 HashSet 将字符存储在当前窗口 [i,j)(最初 j=i)中。 然后我们向右侧滑动索引 j,如果它不在 HashSet 中,我们会继续滑动 j。直到 s[j] 已经存在于 HashSet 中。此时,我们找到的没有重复字符的最长子字符串将会以索引 i开头。如果我们对所有的 iii 这样做,就可以得到答案。
public class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
Set<Character> set = new HashSet<>();
int ans = 0, i = 0, j = 0;
while (i < n && j < n) {
// try to extend the range [i, j]
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;
}
}
时间复杂度O(n)
(3)优化的滑动窗口:
上述的方法最多需要执行 2n 个步骤。事实上,它可以被进一步优化为仅需要 n 个步骤。我们可以定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口。
也就是说,如果 s[j] 在 [i,j) 范围内有与 j′重复的字符,我们不需要逐渐增加 iii 。 我们可以直接跳过 [i,j′] 范围内的所有元素,并将 iii 变为 j′+1。
使用HashMap
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character,Integer> map=new HashMap<>();
int i=0,j=0;
int sum=0;
int n=s.length();
for(j=0;j<n;j++){
if(map.containsKey(s.charAt(j))){
i=Math.max(i,map.get(s.charAt(j))+1);
}
sum=Math.max(sum,j-i+1);
map.put(s.charAt(j),j);
}
return sum;
}
}
4、寻找两个有序数组的中位数
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
看之前的笔记,有这一篇
5、最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
输入: "cbbd"
输出: "bb"
使用的"马拉车算法",前面文章也有讲解
class Solution {
public String longestPalindrome(String s) {
String str="";
for(int i=0;i<s.length();i++){
str=str+"#"+s.charAt(i);
}
str=str+"#";
int[] pl=new int[2005];
int pos=0;
int Right=0;
pl[0]=1;
for(int i=1;i<str.length();i++){
if(i<=Right){
int j=2*pos-i;
int aa=Right-i;
pl[i]=Math.min(pl[j],aa);
if(pl[i]==0)
pl[i]=1;
int r=i+pl[i];
int l=i-pl[i];
while(r<str.length() && l>-1){
if(str.charAt(r)==str.charAt(l)){
r++;
l--;
pl[i]++;
}
else{
break;
}
}
if(i+pl[i]-1>Right){
Right=i+pl[i]-1;
pos=i;
}
}
else{
pl[i]=1;
int r=i+1;
int l=i-1;
while(r<str.length() && l>-1){
if(str.charAt(r)==str.charAt(l)){
r++;
l--;
pl[i]++;
}
else{
break;
}
}
pos=i;
Right=i+pl[i]-1;
}
}
int max=0;
int j=0;
for(int i=0;i<str.length();i++) {
if(max<pl[i]){
max=pl[i];
j=i;
}
}
max=max-1;
int k1=j+1;
int k2=j-1;
String str1=str.charAt(j)+"";
for(int i=1;i<=max;i++) {
str1=str.charAt(k2)+str1+str.charAt(k1);
k1++;
k2--;
}
str1=str1.replace("#","");
return str1;
}
}
6、Z字形变换
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。
示例:
输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"
输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:
L D R
E O E I I
E C I H N
T S G
代码:
class Solution {
public String convert(String s, int numRows) {
if(numRows==1){
return s;
}
List<StringBuffer> list=new ArrayList<>();
for(int i=0;i<numRows;i++){
list.add(new StringBuffer());
}
int flag=1;
int current=1;
list.get(0).append(s.charAt(0));
for(int i=1;i<s.length();i++){
list.get(current).append(s.charAt(i));
if(current==0){
current=1;
flag=1;
}
else if(current==numRows-1){
current=numRows-2;
flag=-1;
}
else{
if(flag==1)
current=current+1;
else
current=current-1;
}
}
String str="";
for(int i=0;i<numRows;i++){
str=str+list.get(i).toString();
}
return str;
}
}
注:
(1)flag和current用的好 ,flag表示是向上走还是向下走,current表示当前行
(2)向字符串中添加一个字符,用StringBuffer比较简单,可以直接使用append()方法,而string中如此方法,最后别忘了再转换成 string就行
7、整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例:
输入: 123
输出: 321
输入: -123
输出: -321
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
注:此题关键点在于反转后的数可能超出整数范围
(1)使用java字符串的反转进行,通过异常来判断反转所得的数是否异常
class Solution {
public int reverse(int x) {
int a,b;
int c=(int)Math.pow(2,31);
a=c-2*c;
b=c-1;
if(x<a || x>b){
return 0;
}
String str=String.valueOf(x);
String str1="";
String str2="";
if(x<0){
str1="-"+str1;
str2=str.substring(1);
}else{
str2=str.substring(0);
}
StringBuffer str3=new StringBuffer(str2);
String str4=str3.reverse().toString();
str1=str1+str4;
try {
int d=Integer.parseInt(str1);
return d;
}catch (Exception e) {
return 0;
}
}
}
(2)正规的C语言实现
int reverse(int x){
/*
溢出条件有两个,一个是大于整数最大值MAX_VALUE,另一个是小于整数最小值MIN_VALUE,设当前计算结果为ans,下一位为 pop。
(1)从ans * 10 + pop > MAX_VALUE这个溢出条件来看
当出现 ans > MAX_VALUE / 10 且 还有pop需要添加 时,则一定溢出
当出现 ans == MAX_VALUE / 10 且 pop > 7 时,则一定溢出,7是2^31 - 1的个位数
(2)从ans * 10 + pop < MIN_VALUE这个溢出条件来看
当出现 ans < MIN_VALUE / 10 且 还有pop需要添加 时,则一定溢出
当出现 ans == MIN_VALUE / 10 且 pop < -8 时,则一定溢出,8是-2^31的个位数
*/
int a=0;
int b;
int xx=x;
int h=(int)pow(2,31);
int h1=h-2*h;
int h2=h-1;
while(xx){
b=xx%10;
if(a>h2/10 ||(a==h2/10 && b>7))
return 0;
if(a<h1/10 || (a==h1/10 && b<-8))
return 0;
a=a*10+b;
xx=xx/10;
}
return a;
}