leetcode
- 数组
- 链表
- 基础知识
- leetcode 203 移除链表元素
- leetcode 707 设计链表
- leetcode 206 反转链表
- leetcode [24. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)
- leetcode[19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
- leetcode[面试题 02.07. 链表相交](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/)
- leetcode [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)
数组
基础知识
- 数组是存放在连续内存空间上的相同类型数据的集合。
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
- 因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址;例如:删除下标为3的元素,需要对下标为3的元素后面的所有元素都要做移动操作
- C++,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
- 数组的元素是不能删的,只能覆盖。
二分查找
二分法:
O(logn)
- 二分法的前提条件: 数组为有序数组,数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
- 循环不变量规则:区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while循环寻找中每一次边界的处理都要坚持根据区间的定义(不变量)来操作,这就是循环不变量规则。(左闭右开/左开右闭)
- 二分法区间的定义:一般为两种,左闭右闭即[left, right],左闭右开即[left, right)
[left, right]:
left = 0, right = nums.length - 1;
while (left <= right) 要使用 <=;
if (nu
ms[middle] > target) right = middle - 1,
if (nums[middle] < target) left = middle + 1
[left, right):
left = 0, right = nums.length;
while (left < right),要使用 < ;
if (nums[middle] > target) right = middle
if (nums[middle] < target) left = middle + 1
leetcode 704
题目链接:leetcode 704
代码:
// 1
// if(nums.indexOf(target) !== -1){
// return nums.indexOf(target)
// }else{
// return -1
// }
// 2、 [left,right]
// let middle,left = 0,right = nums.length -1;
// while(left <= right){
// middle = Math.floor((left + right)/2)
// if(nums[middle] > target){
// right = middle -1
// }else if(nums[middle] < target){
// left = middle + 1
// }else{
// return middle
// }
// }
// return -1
// 3、 [left, right)
let middle,left = 0, right = nums.length
while(left < right){
middle = Math.floor((left + right) / 2)
if(nums[middle] > target){
right = middle
}else if(nums[middle] < target){
left = middle +1
}else{
return middle
}
}
return -1
相关题目:
35.搜索插入位置(opens new window)
34.在排序数组中查找元素的第一个和最后一个位置(opens new window)
69.x 的平方根
367.有效的完全平方数
双指针
O(n)
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
-
数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
数组中的元素为什么不能删除,主要是因为以下两点: 数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束,回收内存栈空间)。 C++中vector和array的区别一定要弄清楚,vector的底层实现是array,封装后使用更友好。 -
很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
leetcode 27
解题思路:
-
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组 慢指针:指向更新新数组下标的位置
题目链接:leetcode 27
代码:
// 暴力实现: 第一层for:遍历数组;第二层for:向前覆盖删除的元素
// for(let i =0;i<nums.length;i++){
// if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
// for (let j = i + 1; j < nums.length; j++) {
// nums[j - 1] = nums[j];
// }
// i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
// nums.length--; // 此时数组的大小-1
// }
// }
// return nums.length
// 快慢指针
let slow = 0;
for(let fast = 0; fast < nums.length; fast++){
if(nums[fast] !== val){
nums[slow] = nums[fast]
slow++
}
}
return slow
leetcode 977
题目描述:非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
题目链接:leetcode 977
代码:
var sortedSquares = function(nums) {
/**
数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
双指针:从左右向中间移动,i(左), j(右)
定义一个存平方值的新数组result,大小和nums一致,下标值k从最大向最小方向存值。
i++ : 左边的大,存到新数组result[k]中, k--
j--: 右边的大,存到新数组result[k]中, k--
*/
let i = 0, j = nums.length -1, k = nums.length - 1;
let result = new Array(nums.length)
while(i <= j){
if(nums[i] * nums[i] > nums[j] * nums[j]){
result[k] = nums[i] * nums[i];
k--;
i++;
}else{
result[k] = nums[j] * nums[j];
k--;
j--;
}
}
return result
};
题目推荐:
26.删除排序数组中的重复项
283.移动零
844.比较含退格的字符串
977.有序数组的平方
滑动窗口
O(n)
- 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
- 滑动窗口:不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
- 滑动窗口可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口。
- 滑动窗口的精华:如何移动起始位置
leetcode 209
题目描述:
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
题目链接:leetcode 209
代码:
var minSubArrayLen = function(target, nums) {
/**
在本题中实现滑动窗口,主要确定如下三点:
窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
*/
let start = 0,
sum = 0,
len = 0,
result = Infinity;//子数组长度
for(let end = 0; end < nums.length; end++){
sum = sum + nums[end]
while(sum >= target){ //如果用if,满足一次sums >= target,就跳出当前for循环;所以用while
sum = sum - nums[start];
len = end - start + 1; // len保存的当前的子数组长度 ==》 要找到长度最小的,每次都要比较留下最小的
result = Math.min(result,len)
start++;
}
}
return result === Infinity ? 0 : result
};
相关题目:
904.水果成篮
76.最小覆盖子串
模拟行为
循环不变量原则(左闭右开/左开右闭)
感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的
leetcode 59 螺旋矩阵
题目描述:
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
思路:
本题并不涉及到什么算法,就是模拟过程。
循环不变量原则:循环中,处理边界。
本题依然是要坚持循环不变量原则:
模拟顺时针画矩阵的过程:
填充上行从左到右
填充右列从上到下
填充下行从右到左
填充左列从下到上
由外向内一圈一圈这么画下去。
这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。
这里一圈下来,要画每四条边,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
题目链接:leetcode 59
代码:
var generateMatrix = function(n) {
let startX = startY = 0; // 起始位置
let loop = Math.floor(n/2); // 旋转圈数
let mid = Math.floor(n/2); // 中间位置
let offset = 1; // 控制每一层填充元素个数
let count = 1; // 更新填充数字
let res = new Array(n).fill(0).map(() => new Array(n).fill(0)); // n×n数组
while(loop--){// [row,col]
let row = startX, col = startY;
// 上行从左到右(左闭右开)
for (; col < startY + n - offset; col++) {
res[row][col] = count++;
}
// 右列从上到下(左闭右开)
for (; row < startX + n - offset; row++) {
res[row][col] = count++;
}
// 下行从右到左(左闭右开)
for (; col > startY; col--) {
res[row][col] = count++;
}
// 左列做下到上(左闭右开)
for (; row > startX; row--) {
res[row][col] = count++;
}
// 更新起始位置
startX++;
startY++;
// 更新offset
offset += 2;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2 === 1) {
res[mid][mid] = count;
}
return res;
};
相关题目:
54.螺旋矩阵
剑指Offer 29.顺时针打印矩阵
链表
基础知识
- 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域,一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。 - 链表的类型
单链表 / 双链表 / 循环链表 - 链表的存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。 - 链表的定义
- 链表的操作
删除节点
添加节点
class ListNode {
val;
next = null;
constructor(value) {
this.val = value;
this.next = null;
}
}
- 链表操作的两种方式:
1、直接使用原来的链表来进行删除操作:处理头节点和其他非头节点
return head
2、设置一个虚拟头结点在进行删除操作。
- 定义临时指针: cur,指向题目中的头节点,来操作临时指针cur,来遍历链表,使得返回的head头指针不会被改变
- cur = dynamicHead ; return dynamicHead.next
- 修改某一个节点的指向(删除/添加节点等): 操作指针一定是该节点的前一个节点的指针
leetcode 203 移除链表元素
思路:
链表操作的两种方式:
直接使用原来的链表来进行删除操作:处理头节点和其他非头节点
设置一个虚拟头结点在进行删除操作。
删除的是头结点:
直接使用原来的链表来进行移除:将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。
设置一个虚拟头结点在进行删除操作:原链表的所有节点(包括头节点)就都可以按照统一的方式进行移除了
题目描述:
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
题目链接:leetcode 203
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
// 方法一、直接使用原来的链表来进行移除节点操作
while(head !== null && head.val === val){//移除头节点
head = head.next
}
let cur = head; //定义临时指针cur进行遍历链表,使head的值不变,返回原先链表的头节点
while(cur !== null && cur.next !== null){//移除非头节点
if(cur.next.val === val){
cur.next = cur.next.next
}else{
cur = cur.next
}
}
return head
// // 设置一个虚拟头结点在进行移除节点操作 (虚拟节点.next = head),因此不需要单独去判断头节点
// let dyanmiHead = new ListNode(0,head) // 定义虚拟头节点(虚拟头节点.val = 0; 虚拟头节点.next = head)
// let cur = dyanmiHead // 定义临时指针来遍历链表,使得dyanmiHead的链表不被改变
// while(cur !== null && cur.next !== null){
// if(cur.next.val === val){
// cur.next = cur.next.next
// }else{
// cur = cur.next
// }
// }
// return dyanmiHead.next
};
leetcode 707 设计链表
思路:
题目描述:
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
题目链接:leetcode 707
代码:
leetcode 206 反转链表
思路:双指针
题目描述:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
题目链接:leetcode 206
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
/**
双指针:
fast(指向头节点的前一个指针),slow(指向头指针),temp(临时指针,保存slow.next,为了形成链)
*/
let fast = null, slow = head, temp = null
while(slow !== null){
temp = slow.next;
slow.next = fast;
// 注意顺序
fast = slow;
slow = temp
}
return fast
};
leetcode 24. 两两交换链表中的节点
题目描述:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进 行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var swapPairs = function(head) {
/**
修改某一个节点的指向: 操作指针一定是该节点的前一个节点的指针
临时的节点:指向要操作的两个节点的前一个节点
*/
// 定义虚拟头节点
let dynamicHead = new ListNode(0,head)
dynamicHead.next = head
// 定义临时节点,指向虚拟头节点
let cur = dynamicHead
// 定义保存指针
let temp = null
let temp1 = null
//边界 : cur.next !== null 为偶数;cur.next.next !== null 为奇数
while(cur.next !== null && cur.next.next !== null){
temp = cur.next;
temp1 = cur.next.next.next
// 交换节点 -- 使得cur.next 不指向 head了,所以之前要定义保存的指针来保存cur.next
cur.next = cur.next.next;
cur.next.next = temp;
temp.next = temp1;
cur = cur.next.next
}
return dynamicHead.next
};
leetcode19. 删除链表的倒数第 N 个结点
思路:
找到倒数第n个节点 --》 操作指针一定指向倒数第n个节点的前一个节点
双指针:如果要删除倒数第n个节点,让fast移动n+1步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow.next所指向的节点就可以了。
题目描述:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let dynamicHead = new ListNode(0,head)
let fast = slow = dynamicHead;
n = n+1
// fast 向后走n+1
while(n !== 0 && fast !== null){
fast = fast.next
n--
}
// fast 和 slow同时走
while(fast !== null){
fast = fast.next
slow = slow.next
}
// 删除
slow.next = slow.next.next
return dynamicHead.next
};
leetcode面试题 02.07. 链表相交
题目描述:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
leetcode 142. 环形链表 II
思路:
判断是否有环:快慢指针,指针相遇即有环;
从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个 链表有环。
fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
找环的入口:
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入 口节点节点数为 z。
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递, 仅仅是为了标识链表的实际情况。
不允许修改 链表。

被折叠的 条评论
为什么被折叠?



