随缘刷题算法学习笔记


前言

害,本来标题叫做“每日一题算法,你的快乐源泉” 后面发现,男人就是大猪蹄子,还每日一题,每个星期一题都难保证,妈呀,没次回头看才发现又欠下一堆了,不忍心标题骗自己,就改了现在你看到得了~~


补充
1.一切数据结构都可以,转变为数组或链表问题
2.空间复杂度:执行该算法需要多少内存空间
3.时间复杂度:执行该算法需要多少时间

一、第一天:剑指 Offer 03. 数组中重复的数字 (6/28)

题目剑指 Offer 03. 数组中重复的数字

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof

解题思路

这题应该是不难的,解决的办法应该很多,本着我这一渣渣,就不去考虑啥了,结出先是我现阶段的思考,优化交给不断变强变秃后的自己来优化吧!

这题感觉最暴力的是用两个for循环来,但考虑到这样太low了,不符合我英俊潇洒的气质,我换了种解法,我们用map方法来解,把每一个首次出现的加入到map里面,如果后面再出现就直接return。代码如下:

var findRepeatNumber = function(nums) {
    const map = new Map;
    for(let i = 0 ; i <= nums.length ; i++){
        if(!map.has(nums[i])){
        map.set(nums[i],1)
        }else{
            return nums[i]
        }
    }
    return undefined
};

呜呜呜,好菜我!!

补充知识

简介Set

  • Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

最大特点:

  • 能保证里面的值不重复

方法

  • add():添加值
  • delete():返回true或false来告知是否成功删除
  • has():判断是否存在
  • forEach():遍历
    map

简介Map

  • 是一种键值对的结构,具有极快的查找速度

方法

  • set(key ,value):添加(如果添加的是已经存在的值,就会覆盖)
  • get(key):获取
  • size():获取长度
  • has(key):判断是否存在
  • delete(key):删除
  • forEach/for…of:遍历

二、第二天:剑指 Offer 04. 二维数组中的查找 (6/29)

剑指 Offer 04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof

解题思路
从右上角出发,小于目标值则向左移一位,大于就向下(因为我们要找一个数字只能从向右或向下找,那么我们要排除定位出目标,也要这样剔除无用的行和列)

var findNumberIn2DArray = function(matrix, target) {
    //判断条件,基本要求啦
    if(matrix == null || matrix.length == 0) {
        return false;
        }
        //获取到长宽~~
        let m = matrix.length, n = matrix[0].length;
        let  row = 0, col = n - 1;
        while(row < m && col >= 0) {
            if(matrix[row][col] > target) {
                col--;
            }else if(matrix[row][col] < target) {
                row++;
            }else {
                return true;
            }
        }
        return false;
};

补充知识

简单说一下数组和链表的区别吧!

  • 数组是最常用的了一块连续的空间,查询速度快,但数组是没有索引的,对于要经常移动增删改查的话,效率会低很多。适用于不长改变操作的情况下。
  • 链表是一块可不连续的动态空间,长度可变,每个节点要保存相邻结点指针,因为保存了指针,所以对于数据的增删改查很便捷高效。
  • 简单来说,数组便于查询,链表便于插入删除。
  • 一般在c++/java里数组是需要订长度的,但JavaScript中不需要,可以动态增长。

三、第三天:题目:剑指 Offer 10- II. 青蛙跳台阶问题(7/11)

题目剑指 Offer 10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof

解题思路

这题我们这么想,当我们走到最后一步的时候,那么前面就还有 n-1 或 n-2 步要走,那么我们是不是要知道当有 n-1 步和 n-2 步时有多少种方法,我们就知道走 n 步时共有多少种了 ,就得出转移方程 f(n) = f(n-1) + f(n-2),得出转移方程就容易啦,同时要注意一下边界问题,比如这里我们要判断当为n的值为0和1时的值。判断条件是常常要考虑到的。

var numWays = function (n) {
  if (!n || n === 1) return 1;

  let db = []
  db[0] = 1; 
  db[1] = 2; 

  for (let i = 2; i <= n; i++) {
      db[i] = (db[i-1] + db[i-2])%1000000007
  }
  return db[n-1];
};

呜呜呜,好菜我!!

补充知识
这题类似背包问题的,是动态规划类的题目。动态规划的题有下面的一些特性。

第一步

  • 确定状态
    • 最后一步,除了这一步的所有前面总和
      • 以下面的背包问题来了说就是,最后一枚银币是a,总和为total-a,因为是最优,所以这样前面一定是最优的
      • 所以我们的问题变为如何用最少的硬币拼出total-a,这里就变为子问题了
      • 所以我们的状态就设为:f(x) = 最小用多少硬币拼出X
    • 子问题

第二步

  • 转移方程
    • 就是上面的子问题转化为方程

第三步

  • 初始条件
  • 边界问题

四、第四天:剑指 Offer 11. 旋转数组的最小数字(7/26)

剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof

解题思路
在这里插入图片描述

首先我们根据题意可以画出上面的这张图,我们可以看到,中点的位置可能在左边,也可能在右边,也或者刚好在分割点。那么就有三种情况要考虑了。

  1. (numbers[mident] < numbers[left])
    这时最小值肯定在mident的左边,所以left = mident
  2. (numbers[mident] > numbers[left])
    这时,最小值肯定在mident的右边,所以right = mident + 1。
  3. (numbers[mident] = numbers[left])
    这时,就刚好了。

var minArray = function(numbers) {
    let right = 0;
    let left = numbers.length - 1;
    while (right < left) {
        const mident = Math.floor((right+left)/2);
        if (numbers[mident] < numbers[left]) {
            left = mident;
        } else if (numbers[mident] > numbers[left]) {
            right = mident + 1;
        } else {
            left -= 1;
        }
    }
    return numbers[right];
};

补充知识
看到有序的情况下,我们就应该考虑二分法查找。
二分法查找我们要确定中点,定义左右,通过大小,不断的缩小范围,来确定出我们要找的值。


五、第五天:1. 两数之和(7/28)

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum

最简单粗暴的办法就是两个循环啦,但是这样无疑,导致的复杂度比较高,想想当年,第一次刷到这题时就是这么干的,那么现在当然不要这么干啦,我们通过得出目标与数组中每一个的差值,来构建字典,并且通过字典查询是否存在符合的值就可以判断了

    var twoSum = function(nums, target) {
    let map = new Map();
    nums.forEach((item,index) => {
        if(map.has(target - item)){
            return [map.get(target - item), index];
        }else{
            map.set(item, index);
        }
    })
    return [];
};

上面的写法是错的,不知道你发现没,开始十万个为什么~~,后面好好的去看了forEach的官方文档才发现。也可以看我写的另外篇关于 foreach 的文章
其实,上面我们直接用 for 就好了。


六、第六天:53. 最大子序和(8/01)

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-subarray/

   let nums = [-2,1,-3,4,-1,2,1,-5,4]
   var maxSubArray = function(nums) {
  let max = nums[0];
  let allmax = nums[0];
  for (let i = 1  , n = nums.length; i < n ; i++) {
      allmax = Math.max(allmax + nums[i] , nums[i]);
      max = Math.max(allmax , max)
  }
  return max
};

这里他要求连续的最大数组和,那么我们可以先找出所以连续的数组中,每一个所能达到的最大值。

问题是如何获得呢?我们创建一个数组来存放所能达到的最大值的每一段连续数组,比如我们从 numerous[0] 开始开始向前累加,那么下一位是 1 那么比原来的大,我们就把numerous[0],加入到数组中,作为第一项。另外把起始位更新到下一位,重新开始向前累加

pre = Math.max(pre + x, x);

如果前边累加后还不如自己本身大,那就把前边的都扔掉,从此自己本身重新开始累加。反之添加。


七、第七天:88. 合并两个有序数组(8/02)

88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

来源:来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-sorted-array

   var merge = function(nums1, m, nums2, n) {
    let index1 = m - 1, index2 = n - 1, indexAll = m + n - 1;
    var cur;
    while (index1 >= 0 || index2 >= 0) {
        if (index1 === -1) {
            cur = nums2[index2--];
        } else if (index2 === -1) {
            cur = nums1[index1--];
        } else if (nums1[index1] > nums2[index2]) {
            cur = nums1[index1--];
        } else {
            cur = nums2[index2--];
        }
        nums1[indexAll--] = cur;
    }
};

首先这里我一开始的思考是从索引为0开始考虑合并,但是这时发现会出现大家都一样的情况下,要把前面的数组放前面的情况,似乎麻烦了点,这样的话,好像还得考虑有重复时,前面数组的元素还得考虑后移(数组中往前面添加元素,后面的元素都得全部后移一位)。

这时就考虑到为什么不从后面开始添加,并且谁大就放谁到追后一位就好了。所以就有了上面的解决方案了。

其实要是用一些 api 还是很好解决的,两行就搞定了

nums1.splice(m, nums1.length - m, ...nums2);
nums1.sort((a, b) => a - b);

补充说明

splice(index , number , add):

可删除从 index 处开始的number个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。会直接改变数组。

slice(start , end):

返回从数组start 到 end 的元素 ,不会改变数组

sort(sortby):

sort() 方法用于对数组的元素进行排序。会改变数组。会根据 sortby(a ,b) 这个方法,方法返回值大于 0,则位置互换, b 到 a 的前面 。返回值小于或等于0,则位置不变。


八、第八天:509. 斐波那契数(8/03)

509. 斐波那契数

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fibonacci-number

这题也算大家基本在课程上都听老师说过的题目了,看起来,这题就是个递归,简单的一行代码就能实现了。

int fib( N) {
    if (N == 1 || N == 2) return 1;
    return fib(N - 1) + fib(N - 2);
}

但是这种解决方法,无疑是最低效率的,通过下图我们可以看出:
在这里插入图片描述
一层一层遍历下去,并且其中还有很多的重复计算。那么我们这里其实可以有一种思想就是通过建立一个备忘录(哈希表),来记录出现过的。不就能大大减少没必要的计算,实现对算法的剪枝。其实我们所有的算法优化都是对暴力穷举的优化,当我们能暴力解出问题后,我们就能针对其中那个地方,可以调优。

var fib = function(n) {
    if(n === 0) return 0;
    let dp = [];
    dp[0] = 0 ;
    dp[1] = 1;
    for(let i = 2 ; i<=n ; i++ ) {
        dp[i] = dp[i-1] + dp[i-2]
    }
    return dp[n]
};

上面问题的优化后的解决办法,采用自底向上思想。

好吧,今天我就是想偷懒,随便做了一题~~~


九、第九天:350. 两个数组的交集 II(8/04)

350. 两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii


var intersect = function(nums1, nums2) {
    let map = new Map();
    let arr =[];
    for(let i of nums1) {
        let times = map.get(i)
        if(times) {
            times++
            map.set(i,times)
        }else {
            map.set(i, 1)
        }
    }
    for(let i of nums2){
        let times = map.get(i)
        if(times > 0) {
            arr.push(i)
            times--
            map.set(i,times)
        }
    }
    return arr
};

这题一看就会想到用哈希表~~~然后统计出现次数。构造好哈希表后,去和另外个数组比较,出现了就把它添加到我们的结果数组中,同时次数减一。并且当次数要大于0。

首先对两个数组进行排序,然后使用两个指针遍历两个数组。

另外个解法就是:排序加指针

初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。


十、第十天:121. 买卖股票的最佳时机(8/05)

121. 买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock

var maxProfit = function(prices) {
    let localMax=0, max=0;
    for (let i=1;i<prices.length;i++) {
        localMax = Math.max(0, localMax += prices[i]-prices[i-1]);
        max = Math.max(localMax, max);
    }
    return max;
};

我们转变一下不就变为最大子序列和了
[7,1,5,3,6,4]
[-6,4,-2,3,-2]
首先我们思考这个点买不买,买了第二天就亏了,那七这个点肯定就不买了,那么我们确定买不买后,我们确定的就是什么时候卖了,我们下一天卖的钱,等于前一天赚到的加上下一天价格减去前一天的。

十一、第一天:566. 重塑矩阵(8/06)

566. 重塑矩阵
在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。

给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。

重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。

如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reshape-the-matrix

var matrixReshape = function(mat, r, c) {
	//判断是否有效
    const m = mat.length;
    const n = mat[0].length;
    if (m * n != r * c) {
        return mat;
    }
    //创建多维数组
    const ans = new Array(r).fill(0).map(() => new Array(c).fill(0));
    //填入数据
    for (let x = 0; x < m * n; ++x) {
        ans[Math.floor(x / c)][x % c] = mat[Math.floor(x / n)][x % n];
    }
    return ans;
};

这题的关键在于理解下面这两个式子

  1. (i,j)→i×n+j (行数为 m,列数为 n)

  2. i=x / n , j=x % n

    所以就得出下面这个关键的式子:

ans[Math.floor(x / c)][x % c] = mat[Math.floor(x / n)][x % n];

十二、第十二天:322. 零钱兑换(8/07)

322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change


var coinChange = function(coins, amount) {
    // 本题采用 自底向上 的动态规划解法
    /*
    首先,构造出amount+1的数组,
    之所以+1,是为了保障最后的金额(最初的原始金额)有位置可以存放
    例如:amount[11]存放着总金额11的最少金币组合
    */
    // 数组中每一项都事先赋为正无穷,便于与最小值的判断
    let dp = new Array(amount + 1).fill(Infinity);
    // 首先预先赋值为0,因为金额0的解法有0种
    dp[0] = 0;

    /*
    破题关键
    每种金额的解法至1金币始,循环到金额amount为止。
    每次外层for循环时,内部的for...of循环来判断是否可用现有的金币组合来组成amount金币量
    举例:amount为11,coins为[1,2,5],则取以下解法的最小值
    coins为1时,amount[11] = 1(利用硬币金额1来解,故占一个金额的位置) + amount[11-1](假设已知,且为最小值)
    coins为2时,amount[11] = 1(利用硬币金额2来解,故占一个金额的位置) + amount[11-2](假设已知,且为最小值)
    coins为5时,amount[11] = 1(利用硬币金额5来解,故占一个金额的位置) + amount[11-5](假设已知,且为最小值)
    */
    for(let i = 1; i <= amount; i++) {
        for(let coin of coins) {
            if (i - coin >= 0) {
                // dp[i]本身的解法 和 dp[当前的总金额i(即amount) - 遍历的icon] + 1(遍历的icon) 的解法的最小值
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
    }

    // 如果结果为无穷大,则无解
    return dp[amount] === Infinity ? -1 : dp[amount];
};

其实这题很早前做过,也算动态规划的入门题了,但我想说的是,这题和上面的斐波那契数一样,都有优化的方法,就是创建备忘录,来减少重复的计算。同时明确,动态规划中,我们要针对性的找出转移方程,把问题化简。如何得出转移方程:

就如算法小抄里面写到的:(这个网站不错,学习算法可以多参考)
在这里插入图片描述

十三、第十三天:46. 全排列(8/08)

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations

var permute = function(nums) {
    let res = [];
    let set = new Set();
    backtracking();
    return res;

    function backtracking() {
        if (set.size === nums.length) {
            res.push(Array.from(set));
            return;
        }
        for (let i = 0; i < nums.length; i++) {
            if (set.has(nums[i])) continue; // 已经上一轮选择过了,跳过
            set.add(nums[i]);//加入选择
            backtracking();//递归继续选择没有选择过的
            set.delete(nums[i]);//撤销返回上一步
        }
    }
};

这题涉及的就是一个回溯算法的知识点了,回溯的模板如下:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

那么简单来说,就是我们在进入下一步前做出选择,在递归之后撤销刚才的选择,那么我们就能得到每个节点的选择列表和路径。
在这里插入图片描述

由于就是暴力遍历全部的结果,所以复杂度很高~~~

十四、第十四天:51. N 皇后(8/08)

51. N 皇后
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens


var solveNQueens = function(n) {
    const res = [];
    // 棋盘的初始化
    const board = new Array(n).fill('.').map(() => new Array(n).fill('.'));
    backtrack(board, 0);
    return res;

    function backtrack (board, row) {
        if (row === board.length) {
            res.push(board.map(item => item.join('')))
            return;
        }

        for (let col = 0; col < n; col++) {
            //判断是否已经选择过
            if (!isValid(board, row, col)) continue
                // 没有放过,放置皇后
                board[row][col] = "Q";
                //递归
                backtrack(board, row + 1);  
                //回退              
                board[row][col] = '.';   
        }
    }
};
//判断是否能放置皇后
function isValid (board, row, col) {
    const n = board.length;
    // 检查列中
    for (let i = 0; i < row; i++) {
        if (board[i][col] === 'Q') {
            return false;
        }
    }
    //右上方
    for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
        if (board[i][j] === 'Q') {
            return false;
        }
    }
    //左上方
    for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
        if (board[i][j] === 'Q') {
            return false;
        }
    }
    return true;
}

话说我们上面做了全排列,了解了回溯,咋能不来道这么金典的N皇后的问题呢,其实我们可以发现,这个问题可以看为一个多叉树的问题,那么是不是我们遍历全部,找出符合的~~其实也就和上面全排列的一个意思的,不同处在于我们的判断条件。这里判断能不能放置就比较麻烦,我们把它单独领出来一个方法判断,那么实际它的框架就如我们上面的一样的。

十五、第十五天:66. 加一(8/09)

66. 加一
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/plus-one

var plusOne = function(digits) {
    for(i = digits.length-1 ; i >= 0 ; i--) {
        if(digits[i] !== 9) {
            digits[i]++
            return digits
        }else {
            digits[i] = 0
        }
    }
    const result = [1,...digits]
    return result
};

养生~~~

十六、第十六天:111. 二叉树的最小深度(8/11)

111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明:叶子节点是指没有子节点的节点。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/


var minDepth = function(root) {
    if(!root) return 0;
    const queue = [root];
    console.log(queue);
    let dep = 0;
    while(true) {
        let size = queue.length;
        console.log(size);
        dep++;
        while(size--){
            const node = queue.shift();
            // 到第一个叶子节点 返回 当前深度 
            if(!node.left && !node.right) return dep;
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
    }
};

这里实际就是使用BFC算法,我们从第一层开始,每层每层的去递进,这样的话就是牺牲空间复杂度,同时他能保证我们找到的就是最小的,因为我们每递进一个节点,深度就加一,保证我们遇到的时候就一定是最短的了。

十七、136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1

示例 2:
输入: [4,1,2,1,2]
输出: 4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/single-number

解法

 //异或运算满足交换律
 //a^b^a=a^a^b=b,因此ans相当于nums[0]^nums[1]^nums[2]^nums[3]^nums[4]..... 
// 再根据交换律把相等的合并到一块儿进行异或(结果为0),然后再与只出现过一次的元素进行异或,
// 这样最后的结果就是,只出现过一次的元素(0^任意值=任意值)
var singleNumber = function(nums) {
 let ans = 0;
    for(const num of nums) {
        ans ^= num;
    }
    return ans;
};

十八、剑指 Offer 24. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof

解法

var reverseList = function(head) {
 let cur = head
    let pre = null
    while(cur != null) {
        const next = cur.next
        cur.next   = pre
        pre = cur
        cur = next
    }
    return pre
};

在这里插入图片描述


总结

每天刷题,每天变强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值