一,链表
1. 前序遍历判断回文链表 (反转,快慢指针)
传送门:【LeetCode 直通车】:234 回文链表(简单)
题解:个人认为链表的简单题比其他简单题要难,实现起来边界细节处理容易出错或者脑子转不过弯。
-
总体思路:将链表前一半(向下取整)反转,然后从中间节点和头节点同步遍历链表前半段和后半段,每个位置判断值是否相同。
-
反转:先定义空指针,再从中间节点处的下一个节点遍历链表至结尾,每一个节点利用头插法进行链表反转,时间复杂度O(n/2),空间复杂度O(1)。
-
找中间节点:定义fast和slow两个指针,slow走一步,fast走两步,需要注意的是循环条件fast的下一个和下下一个都不能为空。
-
总体时间复杂度O(n),空间复杂度O(1)。
//头插法
const reverseList=(head)=>{
//head为要插入的
let pre=null;
let curr=head;
while(curr!=null){
let nextTemp=curr.next;
curr.next=pre;
pre=curr;
curr=nextTemp;
}
return pre;
}
//找到中间节点
const findMiddle=(head)=>{
let fast=head;
let slow=head;
while(fast.next!==null&&fast.next.next!==null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
//判断
var isPalindrome = function(head) {
if(head==null) return true;
let mid=findMiddle(head);
mid=mid.next;
let reversedList=reverseList(mid);
let p=head;
while(p.next!=mid){
p=p.next;
}
p.next=reversedList;
p=p.next;
while(p!=null){
if(head.val==p.val){
head=head.next;
p=p.next;
}else{
return false;
}
}
return true;
};
2. 反转链表 (头插法)
传送门:【LeetCode 直通车】:206 反转链表(简单)
解题思路:经典头插法,先定义空指针,在定义一个指针从头节点遍历
-
时间复杂度O(n),空间复杂度O(1)
var reverseList = function(head) {
let pre=null;
let curr=head;
while(curr!=null){
let currNext=curr.next;
curr.next=pre;
pre=curr;
curr=currNext;
}
return pre;
};
3. 合并K个升序链表 (双指针,DFS)
传送门: 【LeetCode 直通车】:23 合并K个升序链表(困难)
解题思路: 先写mergeList函数两两合并,再利用DFS
-
mergeList函数:用两个指针分别指k1,k2两个子链表。小指针前进并将值存储至list数组,将合并后的结果返回,即list数组。
-
主函数:主函数中用DFS对每个子链表进行两两合并。
const mergeList=(k1,k2)=>{
let list=[];
let i=0;
let j=0,k=0;
while(j<k1.length&&k<k2.length){
if(k1[j]<k2[k]){
list[i]=k1[j];
j++;
i++;
}else{
list[i]=k2[k];
k++;
i++
}
}
while(k<k2.length){
list[i]=k2[k];
i++;
k++;
}
while(j<k1.length){
list[i]=k1[j];
i++;
j++;
}
return list;
}
var mergeKLists = function(lists) {
function dfs(i,j){
const m=j-i;
if(m===0) return null;
if(m===1) return lists[i];
const left=dfs(i,i+m/2);
const right=dfs(i+m/2+1,j);
return mergeList(left,right);
}
return dfs(0,lists.length);
};
4. K个一组翻转链表 (头插法)
传送门:【LeetCode 直通车】:25 K 个一组翻转链表(困难)
解题思路: 先写myReverse函数进行子链表翻转,主函数中遍历链表,对每段子链表进行处理
-
链表反转函数:老生常谈,核心代码是链表的头插法,需要注意的是,此函数中顺便处理了子链表尾部的连接,此处定义一个指针connect来进行尾部连接。
-
主函数:主函数中需要处理子链表首尾连接的关键指针是pre和af,分别指向头节点的上一个和尾节点的下一个,为对每个子数组统一处理,需要再链表最开头新建一个节点。(ps:此处关于af的指针可以去掉,因为链表反转函数顺便处理了子链表尾部的连接)
const myReverse = (head, tail) => {
let prev = tail.next;
let p = head;
while (prev !== tail) {
const nex = p.next;
p.next = prev;
prev = p;
p = nex;
}
return [tail, head];
}
var reverseKGroup = function(head, k) {
const hair = new ListNode(0);
hair.next = head;
let pre = hair;
while (head) {
let tail = pre;
// 查看剩余部分长度是否大于等于 k
for (let i = 0; i < k; ++i) {
tail = tail.next;
if (!tail) {
return hair.next;
}
}
const af = tail.next;
[head, tail] = myReverse(head, tail);
// 把子链表重新接回原链表
pre.next = head;
tail.next = af;
pre = tail;
head = tail.next;
}
return hair.next;
};
5. 环形链表 (快慢指针)
传送门: 【LeetCode 直通车】:141 环形链表(简单)
解题思路: 快慢指针,需要注意的是边界条件,一个节点和两个节点肯定无环,故fast和fast.next不能为空
var hasCycle = function(head) {
let slow=head,fast=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
};
6. 排序链表 (归并排序)
传送门:【LeetCode 直通车】:148 排序链表(中等)
解题思路:
const merge=(head1,head2)=>{
const dummyHead=new ListNode(0);
let temp=dummyHead,temp1=head1,temp2=head2;
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 if (temp2 !== null) {
temp.next = temp2;
}
return dummyHead.next;
}
const toSortList = (head,tail) => {
if(head===null) {
return head;
}
if(head.next===tail) {
head.next=null;
return head;
}
let slow=head,fast=head;
while(fast!==tail) {
slow=slow.next;
fast=fast.next;
if(fast!=tail) {
fast=fast.next;
}
}
const mid=slow;
return merge(toSortList(head,mid),toSortList(mid,tail));
}
var sortList = function(head) {
return toSortList(head,null);
};
7. 相交链表
传送门:【LeetCode 直通车】:160 相交链表(简单)
解题思路: 较长的链表的遍历指针先行两指针之差步,然后两指针同步向前,若两指针所指节点相同,返回该节点。
var getIntersectionNode = function(headA, headB) {
let lena=0,lenb=0;
let p1=headA,p2=headB;
let long,short;
while(p1!=null){
lena++;
p1=p1.next;
}
while(p2!=null){
lenb++;
p2=p2.next;
}
lena>lenb?(long=headA,short=headB):(long=headB,short=headA);
let diff=Math.abs(lena-lenb);
for(let i=1;i<=diff;i++){
long=long.next;
}
let tag=null;
while(short){
if(short==long){
tag=short;
return tag;
}
short=short.next;
long=long.next;
}
return tag;
};
二,字符串
1. 最长回文子串 (动态规划)
传送门:【LeetCode 直通车】:5 最长回文子串(中等)
解题思路: 此题动态规划可解,试想一个回文串的子串肯定也是一个回文串,我们可以根据这个建立动态规划方程:当前状态=上一个状态&上一状态子串左边和右边的字符相同。
var longestPalindrome = function(s) {
let len=s.length;
let res='';
let dp=Array.from(new Array(len),()=>new Array(len).fill(false));
for(let i=len-1;i>=0;i--){
for(let j=i;j<len;j++){
dp[i][j]=s[i]==s[j]&&(j-i<2||dp[i+1][j-1]);
if(dp[i][j]&&j-i+1>res.length){
res=s.substring(i,j+1);
}
}
}
return res;
};
2. 最长公共前缀
传送门:14 最长公共前缀(简单)
解题思路: 初始化一个字符串,维护这个字符串始终为每一步的最短前缀。
-
时间复杂度O(n*max(strs.length)),空间复杂度O(max(strs.length))
var longestCommonPrefix = function(strs) {
if(strs.length==0){
return "";
}
let ans=strs[0];
for(let i=0;i<strs.length;i++){
for(var j=0;j<Math.min(strs[i].length,ans.length);j++){
if(ans[j]!=strs[i][j]){
break;
}
}
ans=ans.substr(0,j)
}
return ans;
};
3. 无重复字符的最长子串 (动态规划)
传送门:【LeetCode 直通车】:3 无重复字符的最长子串(中等)
解题思路:动态规划,无重复字符串的子字符串肯定也无重复,所以状态转移方程为:当前状态=上一个状态&当前字符是否已存在于子串中。
-
Tn:O(n*n),Sn:O(n) 。ps:这个题跑出来的时间和空间都不太好
var lengthOfLongestSubstring = function(s) {
let n=s.length;
let len=1;
let dp=Array.from(new Array(n).fill(false));
if(s==''){
return 0;
}
for(let i=0;i<n-1;i++){
dp[i]=true;
for(let j=i+1;j<n;j++){
let str=s.substring(i,j);
dp[j]=dp[j-1]&&str.indexOf(s[j])==-1;
if(dp[j]){
if(j-i+1>len){
len=j-i+1;
}
}else{
break;
}
}
}
return len;
};
4. 最小覆盖子串 (滑动窗口)
传送门:【LeetCode 直通车】:76 最小覆盖子串(困难)
解题思路:滑动窗口 ,思路是遍历字符串并维护一个窗口,t字符串的长度记为len,计数器count=0,每遍历一个字符,判断该字符是否是否属于t...
var minWindow = function(s, t) {
let need = {}, window = {};
for (let c of t) {
if (!need[c]) need[c] = 1;
else need[c]++;
}
let left = 0, right = 0;
let valid = 0, len = Object.keys(need).length;
let minLen = s.length + 1, minStr = '';
while (right < s.length) {
const d = s[right];
right++;
if (!window[d]) window[d] = 1;
else window[d]++;
if (need[d] && need[d] === window[d]) {
valid++;
}
while (valid === len) {
if (right - left < minLen) {
minLen = right - left;
minStr = s.slice(left, right);
}
console.lo
let c = s[left];
left++;
window[c]--;
if (need[c] && window[c] < need[c]) {
valid--;
}
}
}
return minStr;
};
三,数组
1. 最长连续递增序列 (双指针)
传送门:【LeetCode 直通车】:674 最长连续递增序列(简单)
var findLengthOfLCIS = function(nums) {
let ans=1;
let i=0,j=0;
while(j<nums.length) {
if(i<j&&nums[j]<=nums[j-1]){
i=j;
}
ans=Math.max(j-i+1,ans);
j++;
}
return ans;
};
2. 乘最多水的容器
传送门:【LeetCode 直通车】:11 盛最多水的容器(中等)
var maxArea = function(height) {
let area=0;
for(let i=0,j=height.length-1;i<j;){
area=Math.max(area,(j-i)*Math.min(height[i],height[j]));
height[i]>height[j]?j--:i++;
}
return area;
};
3. 删除有序数组中的重复项
传送门:【LeetCode 直通车】:26 删除有序数组中的重复项(简单)
var removeDuplicates = function(nums) {
let i=0;j=1;
if(nums.length==0){
return 0;
}
while(j<nums.length){
if(nums[j]!==nums[j-1]){
i++;
nums[i]=nums[j];
}
j++;
}
return i+1;
};
4. 和为K的子数组
传送门:【LeetCode 直通车】:560 和为K的子数组(中等)
var subarraySum = function(nums, k) {
let count=0,sum=0;
let map=new Map();
map.set(0,1);
for(let i=0;i<nums.length;++i){
sum=sum+nums[i];
if(map.has(sum-k)){
count+=map.get(sum-k);
}
if(map.has(sum)){
map.set(sum,map.get(sum)+1);
}else{
map.set(sum,1);
}
}
return count;
};
5. nSum问题
传送门: 【LeetCode 直通车】:1 两数之和(简单)
【LeetCode 直通车】:167 两数之和 II - 输入有序数组(简单)
var twoSum = function(nums, target) {
let idx=new Map();
for(let j=0;;j++){
const x=nums[j];
if(idx.has(target-x)) return [idx.get(target-x),j];
idx.set(x,j);
}
};
var twoSum = function(numbers, target) {
let left=0,right=numbers.length-1;
while(left<right){
if(numbers[left]+numbers[right]===target) return [++left,++right];
else if(numbers[left]+numbers[right]>target) right--;
else left++;
}
};
var threeSum = function(nums) {
nums.sort((a,b)=>a-b);
const n=nums.length;
let ans=[];
for(let i=0;i<n-2;i++){
if(i>0&&nums[i]===nums[i-1]){
continue;
}
if(nums[i]+nums[i+1]+nums[i+2]>0) break;
if(nums[i]+nums[n-1]+nums[n-2]<0) continue;
let j=i+1,k=n-1;
while(j<k){
let temp=nums[j]+nums[k]+nums[i];
if(temp>0) k--;
else if(temp<0) j++;
else{
ans.push([nums[i],nums[j],nums[k]]);
j++;
while(j<k&&nums[j]===nums[j-1]) j++;
k--;
while(k>k&&nums[k]===nums[k+1]) k--;
}
}
}
return ans;
};
var fourSum = function(nums, target) {
nums.sort((a,b)=>a-b);
let n=nums.length;
let ans=[];
if(n<4) return ans;
for(let i=0;i<n-3;i++){
if(i>0&&nums[i]===nums[i-1]){
continue;
}
if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target) break;
if(nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue;
for(let j=i+1;j<n-2;j++){
if(j>i+1&&nums[j]===nums[j-1]){
continue;
}
if(nums[i]+nums[j]+nums[j+1]+nums[j+2]>target) break;
if(nums[i]+nums[j]+nums[n-1]+nums[n-2]<target) continue;
let k=j+1,p=n-1;
while(k<p){
let sum=nums[i]+nums[j]+nums[k]+nums[p];
if(sum>target) p--;
else if(sum<target) k++;
else{
ans.push([nums[i],nums[j],nums[k],nums[p]]);
k++;
while(k<p&&nums[k-1]===nums[k]) k++;
p--;
while(k<p&&nums[p]===nums[p+1]) p--;
}
}
}
}
return ans;
};
6. 跳跃游戏(贪心算法)
传送门:【LeetCode 直通车】:55 跳跃游戏(中等)
var subarraySum = function(nums, k) {
let count=0,sum=0;
let map=new Map();
map.set(0,1);
for(let i=0;i<nums.length;++i){
sum=sum+nums[i];
if(map.has(sum-k)){
count+=map.get(sum-k);
}
if(map.has(sum)){
map.set(sum,map.get(sum)+1);
}else{
map.set(sum,1);
}
}
return count;
};