本文已参与「新人创作礼」活动,一起开启掘金创作之路。
力扣 -- 数据结构二周计划
第一天 -- 数组
1、 存在重复元素
/*
解题思路:创建一个对象,用来存储是否有重复元素,若有重复元素则直接返回true,否则返回false
*/
function containsDuplicate(nums: number[]): boolean {
let hash:object = {}
for(let i =0;i<nums.length;i++){
if(!hash[nums[i]]){
hash[nums[i]] = true
}else{
return true
}
}
return false
};
2、最大子数组和
/*
解题思路:利用动态规划,每次比较当前元素的前一个元素是否比0大,若比0大就当前元素保存前一个元素与当前元素的最大和
随后再定义max 动态保存最大的nums【i】
*/
function maxSubArray(nums: number[]): number {
let max:number = nums[0]
let len:number = nums.length
for(let i =1;i<len;i++){
nums[i] += Math.max(nums[i-1],0)
max = Math.max(nums[i],max)
}
return max
};
第二天 -- 数组
1、两数之和
function twoSum(nums: number[], target: number): number[] {
let map = new Map()
let len = nums.length
/* 用 map 来存储计算过的数字
例如 9 - 7 得到的 2 将 7 保存在map里 对象里没有2进行下一步
下次计算 9 - 2 得到的7 对象里有存 7 则将结果返回
*/
for(let i =0;i<len;i++){
let rest = target - nums[i]
if(map.has(rest)){
return [map.get(rest),i]
}
map.set(nums[i],i)
}
};
2、 合并两个有序数组
// 解法一 使用双指针解法
function merge(nums1: number[], m: number, nums2: number[], n: number): void {
let len:number = nums1.length-1
m--
n--
/* 循环比较 num1 从m--下标 与 num2 从n-- 下标开始谁的值大,
大的值就往num1数组的最后面开始往前放
*/
while(n>=0){
if(nums1[m] > nums2[n]){
nums1[len--] = nums1[m--]
}else{
nums1[len--] = nums2[n--]
}
}
};
// 解法二 暴力合并再排序
function merge(nums1: number[], m: number, nums2: number[], n: number): void {
nums1.splice(m,nums1.length-m,...nums2)
nums1.sort((a,b)=>a-b)
};
第三天 -- 数组
1、两个数组的交集 II
var intersect = function(nums1, nums2) {
//长短交换判定
if (nums1.length > nums2.length) {
return intersect(nums2, nums1)
}
const hashmap = new Map()
//将较短的nums1中的元素与个数存入hashmap
for (let num of nums1) {
let count = hashmap.get(num) || 0
hashmap.set(num, count+1)
}
let res = [], index = 0
//遍历nums2,遇到重复则count--并更新hashmap
for (let num of nums2) {
let count = hashmap.get(num) || 0
if (count > 0) {
res[index++] = num
count--
//更新hashmap,以count状态作为判断条件
if (count > 0) {
hashmap.set(num, count)
} else {
hashmap.delete(num)
}
}
}
return res
};
// 双指针做法 + 数组排序
var intersect = function(nums1, nums2) {
// 两数组从小到大排序
nums1.sort((a,b)=>a-b)
nums2.sort((a,b)=>a-b)
// 创建左右指针,结果数组保存结果,index推进结果的下标
let res = [],index=0,left=0,right=0
let len1 = nums1.length,len2 = nums2.length
// 在两个都没达边界时推进
while(left<len1 && right < len2){
// 哪边数组小就推进哪边,直到相等时,即两数组的交集,即把交集的数放进数组里,并推进两数组和结果数组
if(nums1[left] < nums2[right]){
left++
}else if(nums1[left] > nums2[right]){
right++
}else{
// 两边都相等即交集,将结果保存并进行下一波比较
res[index++] = nums1[left]
left++
right++
}
}
return res
};
2、买卖股票的最佳时机
var maxProfit = function(prices) {
// 创建保存数组中最小值
let min = prices[0]
// 记录最大差值
let max = 0
let len = prices.length
for(let i =1;i<len;i++){
// 在每次循环中记录最小的值
let cur = prices[i]
min = Math.min(cur,min)
// 如果当前值比最小值大则更新最大的差值
if(cur > min ) max = Math.max(cur - min,max)
}
return max
};
第四天 -- 数组
1、重塑矩阵
var matrixReshape = function(mat, r, c) {
let m = mat.length,n = mat[0].length
// 要是转化的长度不满足直接返回原数组
if(m * n != r * c) return mat
let res = []
// 将数组换为一维的
mat = mat.flat()
// 按 c 列的数量,一层一层的添加进结果数组并返回
for(let i =0;i<r;i++){
res.push(mat.splice(0,c))
}
return res
};
2、杨辉三角
function generate(numRows: number): number[][] {
let res = []
for(let i =0 ;i<numRows;i++){
// 每一层都初始化为1
const row = new Array(i+1).fill(1)
for(let j =1;j<row.length-1;j++){
// 每一层的数都等于其上一层的左右两数之和,从第三层开始
row[j] = res[i-1][j-1] + res[i-1][j]
}
// 把每一层的数加到结果数组并返回
res.push(row)
}
return res
};
// 用数组的 reduce 方法
var generate = function(numRows) {
let r = Array(numRows)
// 初始化数组第一层为1
r[0] = [1]
for (let i=1;i<numRows;i++) {
//初始化i层 为一个数组
r[i] = []
//到上一层中累加左右两边的数,并添加到该层
r[i-1].reduce((p,c) => {
r[i].push(p+c)
// 返回右边的数作为下一个pre值
return c
},0)
// 补齐最后一个1添加到数组中
r[i].push(1)
}
// 返回结果数组
return r
};
/*
拿第三层举例,到第三层时,上一层为[1,1]
则reduce 上一层[1,1]得到: [1,2]
最后再在最后补上一个1:[1,2,1]就为该层的目标数组
*/
第五天 -- 数组
1、有效的数独
var isValidSudoku = function(board) {
// 创建一个保存的集合
let set = new Set();
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
const number = board[i][j];
if (number === '.') continue;
const colStr = `${number}c${i}`; // 保存每行的元素
const rowStr = `${number}r${j}`; // 保存每列的元素
const sectionStr = `${number}s(${Math.floor(i/3)},${Math.floor(j/3)})`; // 判断3 * 3 格的元素
if (set.has(colStr) || set.has(rowStr) || set.has(sectionStr)) return false; // 若有一个不满足有重复就为false
set.add(colStr);
set.add(rowStr);
set.add(sectionStr);
}
}
return true;
};
2、矩阵置零
var setZeroes = function(matrix) {
let r = matrix.length
let d = matrix[0].length
let res = []
for(let i =0;i<r;i++){
// 找出每行出现的0,并记录下0的纵坐标
if(matrix[i].indexOf(0) !== -1){
matrix[i].forEach((item,index,arr)=>{
if(item == 0) res.push(index)
// 将该行的元素逐个清0
arr[index] = 0
})
}
}
let len = res.length
// 将出现0的纵坐标清0
for(let i =0;i<r;i++){
for(let j =0;j<len;j++){
matrix[i][res[j]] = 0
}
}
};
第六天 -- 字符串
1、字符串中的第一个唯一字符
var firstUniqChar = function(s) {
let len = s.length
for(let i=0;i<len;i++){
// 如果它从前找的下标 等于 它从后开始的下标说明不是重复的
if(s.indexOf(s[i]) === s.lastIndexOf(s[i])){
return i
}
}
return -1
};
2、赎金信
var canConstruct = function(ransomNote, magazine) {
let hash = {}
for(let i of magazine){
// 将大的字符串全部保存下来,重复的就个数+1
hash[i] = ( hash[i] || 0 )+1
}
for(let i of ransomNote){
if(hash[i]){
// 若大的只有1个a,小的有两个a的话,第二次遍历到a时hash[a] == 0 则直接return false
// 或者大的里没有小的字符串,也直接return false
hash[i]--
}else{
return false
}
}
return true
};
3、有效的字母异位词
var isAnagram = function(s, t) {
// 若两字符长度不等,直接返回false
if(s.length !== t.length) return false
let hash = {}
for(let i of s){
// 将字符串全部保存下来,重复的就个数+1
hash[i] = (hash[i] || 0 )+1
}
for(let i of t){
if(hash[i]){
// 若一个只有1个a,另一个有两个a的话,第二次遍历到a时hash[a] == 0 则直接return false
// 或者一个里没有另一个的字符串,也直接return false
hash[i]--
}
else return false
}
return true
};
第七天 -- 链表
1、环形链表
// 第一种使用对象记录每个对象出现的标志,当再次出现时说明有环状返回true
var hasCycle = function(head) {
let node = head
while(node){
if(node.flag) return true
node.flag = true
node = node.next
}
return false
};
// 第二种使用JSON.stringify ,当对象循环引用时会报错说明链表有环状,否则说明无
var hasCycle = function(head) {
try{
JSON.stringify(head)
return false
}catch{
return true
}
};
2、合并两个有序链表
// 利用双指针循环
var mergeTwoLists = function(list1, list2) {
let head = new ListNode(-1)
let preHead = head
while(list1 && list2){
if(list1.val < list2.val)
{
preHead.next = list1
list1 = list1.next
}
else{
preHead.next = list2
list2 = list2.next
}
preHead = preHead.next
}
preHead.next = list1 === null?list2:list1
return head.next
};
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
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
if(head == null) return head
// 初始化链表
let newList = new ListNode(0,head)
let cur = newList
// 循环遍历整个链表,并把相同的节点跳过
while(cur.next){
if(cur.next.val == val){
cur.next = cur.next.next
}else{
cur = cur.next
}
}
// 返回头节点
return newList.next
};
第八天 --- 链表
1、反转链表
var reverseList = function(head) {
if(!head) return head
let node = head
let pre = null,temp
while(node){
temp = node.next
node.next = pre
pre = node
node = temp
}
// 最后的头节点变为pre
return pre
};
// 第二种递归操作
var reverseList = function(head) {
if (head == null || head.next == null) {
return head;
}
const newHead = reverseList(head.next);
// 从倒数第二个节点开始将下一个节点的next指向自己,并将自己的next指向null,完成翻转操作
head.next.next = head;
head.next = null;
// 最后的头节点更换为newHead
return newHead;
};
2、删除排序链表中的重复元素
var deleteDuplicates = function(head) {
// 常规的遍历链表操作去除重复的元素
if(!head) return head
let node = head
while(node.next ){
if(node.val === node.next.val){
node.next = node.next.next
}else{
node = node.next
}
}
return head
};
第九天 --- 栈 / 队列
1、有效的括号
var isValid = function(s) {
if(!s) return true
let len = s.length
let str = {
'{':'}',
'(':')',
'[':']'
}
let stack = []
for(let i =0;i<len;i++){
const cur = s[i]
if(cur == '(' || cur == '[' || cur == '{') stack.push(str[cur])
else{
// 若栈内没有对应的元素则为false
if(!stack.length || stack.pop() !== cur) return false
}
}
// '[' 像这种栈内还有元素的也返回 false
return !stack.length
};
2、用栈实现队列
var MyQueue = function() {
this.MyQueue = []
};
/**
* @param {number} x
* @return {void}
*/
MyQueue.prototype.push = function(x) {
this.MyQueue.push(x)
};
/**
* @return {number}
*/
MyQueue.prototype.pop = function() {
return this.MyQueue.shift()
};
/**
* @return {number}
*/
MyQueue.prototype.peek = function() {
return this.MyQueue[0]
};
/**
* @return {boolean}
*/
MyQueue.prototype.empty = function() {
return this.MyQueue.length == 0
};
/**
* Your MyQueue object will be instantiated and called as such:
* var obj = new MyQueue()
* obj.push(x)
* var param_2 = obj.pop()
* var param_3 = obj.peek()
* var param_4 = obj.empty()
*/
第十天 --- 树
1、二叉树的前序遍历
// 非递归实现,迭代实现
var preorderTraversal = function(root) {
if(!root) return []
let res = []
let queue = [root]
while(queue.length){
let temp = queue.pop()
res.push(temp.val)
if(temp.right) queue.push(temp.right) // 因为栈先进后出所以将right放前
if(temp.left) queue.push(temp.left)
}
return res
};
// 递归实现
var preorderTraversal = function(root,res=[]) {
if(!root) return []
res.push(root.val)
preorderTraversal(root.left,res)
preorderTraversal(root.right,res)
return res
};
2、二叉树的中序遍历
// 非递归实现
var inorderTraversal = function(root) {
if(!root) return []
let res = []
let stack = []
while(stack.length || root){
// 循环遍历左节点
while(root){
stack.push(root)
root = root.left
}
let temp = stack.pop()
res.push(temp.val)
root = temp.right
}
return res
};
//递归实现
var inorderTraversal = function(root,res = []) {
if(!root) return []
if(root.left) inorderTraversal(root.left,res)
res.push(root.val)
if(root.right) inorderTraversal(root.right,res)
return res
};
3、 二叉树的后序遍历
// 非递归实现,前序遍历是根左右,后序为左右根,将前序实现为根右左,再将数组反转即得后序遍历,左右根
var postorderTraversal = function(root) {
if(!root) return []
let res = []
let stack = [root]
while(stack.length){
let temp = stack.pop()
if(temp.left) stack.push(temp.left) // 前序根左右调换为,根右左
if(temp.right) stack.push(temp.right)
res.push(temp.val)
}
return res.reverse() // 将结果反转即为后序
};
4、二叉树的层序遍历
var levelOrder = function(root) {
if(!root) return []
let res = []
let stack = [root]
let count = 0 // 记录当前层级
while(stack.length){
res[count] = [] // 初始化当前层级
let countNum = stack.length // 当前层级的节点数
while(countNum--){ // 遍历当前层级的节点数
let temp = stack.shift() // 从前开始取当前层级的节点
res[count].push(temp.val) // 推入每层的节点值
if(temp.left) stack.push(temp.left)
if(temp.right) stack.push(temp.right)
}
count++ // 层级+1
}
return res
};
第十一天 --- 树
1、二叉树的层序遍历
// 同上题解法
var levelOrder = function(root) {
if(!root) return []
let count = 0
let node = [root]
let res = []
while(node.length){
res[count] = []
let countNum = node.length
while(countNum--){
let temp = node.shift()
res[count].push(temp.val)
if(temp.left) node.push(temp.left)
if(temp.right) node.push(temp.right)
}
count++
}
return res
};
2、二叉树的最大深度
// DFS 深度遍历递归
var maxDepth = function (root) {
let max = 0
const dfs = (root,res = 1) => {
if (!root) return
if(!root.left && !root.right) max = Math.max(res, max)
if (root.left) dfs(root.left, res+1)
if (root.right) dfs(root.right, res+1)
}
dfs(root)
return max
};
// 解法二
var maxDepth = function(root) {
return root == null?0:Math.max(maxDepth(root.left),maxDepth(root.right)) + 1
};
3、对称二叉树
var isSymmetric = function(root) {
if(!root) return true
const isMirror = (left,right)=>{
// 如果判断到叶子节点则返回true
if(!left && !right) return true
// 两个节点都存在,并且值相同,镜像相同,则为true
if(left && right && left.val === right.val
&& isMirror(left.left,right.right)
&& isMirror(left.right,right.left)
) return true
// 都不符合就返回false
return false
}
return isMirror(root.left,root.right)
};
第十二天 --- 树
1、翻转二叉树
// 迭代解法一
var invertTree = function(root) {
if(root == null) return root
let stack = [root]
while(stack.length){
let node = stack.shift()
let temp = node.left
node.left = node.right
node.right = temp
if(node.left) {
stack.push(node.left)
}
if(node.right){
stack.push(node.right)
}
}
return root
};
// 递归解法二
var inverTree = function(root){
if(!root) return null
return {
val:root.val,
left:inverTree(root.right),
right:inverTree(root.left)
}
}
2、路径总和
// 前序遍历解法一
var hasPathSum = function (root, targetSum) {
if (!root) return false
let stack = [[root,root.val]]
while(stack.length){
let [node, sum] = stack.pop()
if(!node.left && !node.right && sum == targetSum) return true
if(node.right) stack.push([node.right,node.right.val + sum])
if(node.left) stack.push([node.left,node.left.val + sum])
}
return false
};
// 递归解法二
var hasPathSum = function (root, targetSum) {
if (!root) return false
let final = false
const dfs = (node, res) => {
if (!node.left && !node.right && res == targetSum) final = true
if(node.left) dfs(node.left, res + node.left.val)
if(node.right) dfs(node.right, res + node.right.val)
}
dfs(root, root.val)
return final
};
第十三天 --- 树
1、二叉搜索树中的搜索
var searchBST = function(root, val) {
// 递归法
if(!root) return null
if(root.val === val) return root
return root.val > val? searchBST(root.left,val):searchBST(root.right,val)
// 迭代法
// while(root){
// if(root.val == val){
// return root
// }
// if(root.val > val) root = root.left
// else root = root.right
// }
// return null
};
2、二叉搜索树中的插入操作
// 递归法最简易,利用二叉搜索树的特性完成插入
var insertIntoBST = function(root, val) {
if(!root) return new TreeNode(val)
if(root.val < val){
if(!root.right) root.right = new TreeNode(val)
else insertIntoBST(root.right,val)
}else{
if(!root.left) root.left = new TreeNode(val)
else insertIntoBST(root.left,val)
}
return root
};
第十四天 --- 树
1、验证二叉搜索树
// 递归
var isValidBST = function(root,min=-Infinity,max=Infinity){
if(!root) return true
if(root.val <= min || root.val >= max) return false
return isValidBST(root.left,min,root.val) && isValidBST(root.right,root.val,max)
};
// 利用中序遍历二叉搜索树为一个升序数组
var isValidBST = function(root){
let res = []
const dfs = (root)=>{
if(!root) return false
root.left && dfs(root.left)
res.push(root.val)
root.right && dfs(root.right)
}
dfs(root)
let len = res.length
for(let i =1;i<len;i++){
if(res[i-1] >= res[i]) return false
}
return true
};
2、两数之和 IV - 输入 BST
// 先遍历元素节点,随后两数之和解法
var findTarget = function (root, k) {
let res = []
const dfs = (root) => {
if (!root) return false
res.push(root.val)
if (root.left) dfs(root.left)
if (root.right) dfs(root.right)
}
dfs(root)
let hash = {}
for (let i = 0; i < res.length; i++) {
let rest = k - res[i]
if (hash[rest]) return true
hash[res[i]] = true
}
return false
};
3、二叉搜索树的最近公共祖先
var lowestCommonAncestor = function (root, p, q) {
// 递归解法
if(root.val > p.val && root.val >q.val) return lowestCommonAncestor(root.left,p,q)
if(root.val < p.val && root.val <q.val) return lowestCommonAncestor(root.right,p,q)
return root
// 迭代解法,先将p跟q的顺序排除q为小,p为大方便后续计算
// if(p.val > q.val) [p,q] = [q,p]
// while (root) {
// if (root.val > p.val && root.val < q.val || root === q || root ===p) return root
// if(root.val > q.val) root = root.left
// if(root.val < p.val) root = root.right
// }
};