这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
1. 两数之和 (two-sum)
标签
- Array
- Map
- 简单
题目
这里不贴题了,leetcode打开就行,题目大意:
在给定数组
中找到 2
个数之和
等于给定值的数字
,结果返回
2 个数字在数组中的下标
。
基本知识
MDN: Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。
-
Map.prototype.get(key)
- 返回键对应的值,如果不存在,则返回undefined。
-
Map.prototype.has(key)
- 返回一个布尔值,表示Map实例是否包含键对应的值。
-
Map.prototype.set(key, value)
- 设置Map对象中键的值。返回该Map对象。
基本思路
- 先建立一个map结构,利用map能很方便建立值和坐标的映射。
- 顺序扫描数组,对每一个元素,如果在 map 中找能组合给定值的另一半数字,找到了直接返回对这两数下标就行。如果找不到就把这个数字存入 map 中,等待扫到另一半数字的时候,再取出来返回结果。
写法实现
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
// 建立map数据结构
const myMap = new Map()
for (i = 0; i < nums.length; i++) {
// (该数字的另一半)= (和 - 该数字)
let reverse = target - nums[i]
if (myMap.has(reverse)) {
// 找到直接输出两数下标
return [myMap.get(reverse), i]
}
// 没找到就先存入 map
myMap.set(nums[i], i)
}
return null
};
2. 两数相加 (add-two-numbers)
标签
- Array
- Linklist
- 中等
题目
这里不贴题了,leetcode打开就行,题目大意:
2 个逆序的链表,要求从低位开始相加,得出结果也逆序输出,返回值是逆序结果链表的头结点。
基本知识
- 链表数据结构
- 数学常识 进位,%10 取数位
基本思路
- 为了避免两链表直接为空,还需要特殊处理建立一个头结点的前置节点 preHead 指向真正的头结点。
- 由于 preHead 结点本身不能变,所以我们用一个指针 cur 来指向新链表的最后一个结点。
- 两链表顺序位相加,注意进位就行,然后每一位值取(和%10),最高位如果还有进位,就加一个最高位
1
- 最后返回前置节点preHead->next就ok了
写法实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
// 为了避免2链表直接为空建立一个头结点的前置节点
let preHead = new ListNode(-1),
// 作为新链表的当前指针,现在指向这个前置节点,之后向后移动
cur = preHead,
sum = 0, // 和
carry = 0 // 进位
// 这两个链表只要有一个不为空就需要相加,两链表为空,进位为1,最高位为1
while (l1 || l2 || carry) {
// 和就等于,两个数位的值相加,再加进位
sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + carry
// 进位就是大于等于 10 进一位
carry = sum >= 10 ? 1 : 0
// 新链表的下一位的值是这个(和 % 10) 比如这两位是 (7 + 5)% 10 这位值是 2 进一位 carry = 1
cur.next = new ListNode(sum % 10)
// 新链表指针后移,原始2列表指针也后移,当然不为空的情况下后移
cur = cur.next
l1 && (l1 = l1.next)
l2 && (l2 = l2.next)
}
// 最后返回头结点,注意不是前置节点
return preHead.next
};
3. 无重复字符的最长子串 (longest-substring-without-repeating-characters)
标签
- Array
- Map
- 滑动窗口思想
题目
这里不贴题了,leetcode打开就行,题目大意:
在一个字符串重寻找没有重复字母的最长子串。
基本知识
- Map操作,看上面第一个问题哈
- 滑动窗口思想
滑动窗口算法思路
- 我们在字符串 S 中使用双指针中的左右指针技巧,初始化left = right = 0,把索引闭区间 [left, right]称为一个「窗口」。
- 我们先不断地增加right指针扩大窗口[left, right],直到窗口中的字符串符合要求(包含了T中的所有字符)。
- 此时,我们停止增加right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含T 中的所有字符了)。同时,每次增加left,我们都要更新一轮结果。
- 重复第 2和第3步,直到right到达字符串S 的尽头。
简单来说就是,第 2 步相当于在寻找一个可行解
,然后第 3 步在优化这个可行解
,最终找到最优解
。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。
拿本题举例就是:滑动窗口的右边界不断的右移,只要没有重复的字符,就持续向右扩大窗口边界。一旦出现了重复字符,就需要缩小左边界,直到重复的字符移出了左边界,然后继续移动滑动窗口的右边界。以此类推,每次移动需要计算当前长度,并判断是否需要更新最大长度,最终最大的值就是题目中的所求。
基本思路
- 用双指针维护一个滑动窗口,用来剪切子串。
- 不断移动右指针,直到遇到重复字符的时候把左指针移到重复字符的下一位。
- 移动指针过程中,记录窗口长度的最大值即为答案。
写法实现
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
let len = s.length,
maxNow = 0, // 临时最大值
left = 0, // 定义左指针坐标
map = new Map(); // 用map来作为滑动窗口,存放字符和对应下标
// 右边指针后移遍历
for (let right = 0; right < len; right++) {
// 如果出现了重复字符,则把左指针移到重复字符的下一位。
// 注意同时满足重复字符的索引大于左指针,表示在窗口内的重复
if (map.has(s[right]) && map.get(s[right]) >= left) {
// 左窗口右滑,直到没重复
left = map.get(s[right]) + 1
}
// 此时长度是 左右两坐标差值 + 1 ,再跟当前最大比较,取最大值
maxNow = Math.max(maxNow, right - left + 1);
// 存下每个字符的下标
map.set(s[right], right)
}
return maxNow
};
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦
搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the reiver monster
,我看到就通过,暗号对不上不加哈