LeetCode初级练习之数组篇

补充说明

随着刷题的量增加,愈发觉得leetcode给出的执行时间存在问题,故去论坛逛了逛,果然不出所料,因此说明一下:
leetCode给出的执行时间会有偏差,甚至同样的代码,提交两次得到的时间都不一样,同等时间复杂度会有几十ms的差别。所以leetcode反馈的运行时间如果差别在一个数量级以上,这时就要考虑优化代码。具体大家可以参考:Leetcode不同语言有哪些减少runtime的方法?。因此这个执行时间大家不必过于关注。若大家看到我关于leetcode练习的博客中存在这方面问题的欢迎指出,十分感谢。

补充时间:2018-05-20

闲聊(可以略过)

算法一直以来是我比较劣势的技能,在上大学接触过,然后就是考研的时候埋头啃过相关书籍,之后基本没怎么碰过了。但是对于大厂来说,算法是必备的基础技能,近段时间比较火的一个app抖音,它的公司-北京字节跳动科技有限公司,是一家纯靠算法起家的公司,我使用过抖音,在我接触过使用抖音的人的反馈就是:内容很有趣。这背后必然是通过算法处理用户数据,分析用户行为然后智能推荐,至少用户的反馈证明这家公司很强。而它旗下还有火山小视频、内涵段子(已凉)、今日头条等用户量很高的app。闲扯了半天,下面直接进入正题,这篇博客主要记录我在LeetCode做的一些题及思考过程(持续更新)。

参考

对于js数组一些方法的实现,这里针对V8引擎,大家可以阅读一下GitHub上开源的V8源码

删除排序数组中的重复项

分析:题目大概意思是,给定一个已排序数组,你需要在该数组上进行操作,去除重复项,且不能使用额外数组。我们可以这样做:

  • 维护两个指针,一个用于保存当前有效长度(不重复元素的长度)
  • 由于数组是有序的,所以重复元素一定相邻

代码:

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function(nums) {
        if (0 == nums.length) {
            return 0;
        }
        // 创建一个指针保存当前有效长度,初始值为1(至少1个)
        var index = 1;
        // 遍历数组
        for (var i = 1; i < nums.length; i++) {
            // 若相邻两个值不同
            if (nums[i] != nums[i-1]) {
                // 存储不重复元素
                nums[index] = nums[i];
                // 有效长度加1
                index++;
            }
        }
        // 截取有效长度
        nums.length = index;
        // 返回有效长度
        return index;
};

执行时间:104ms

完成日期:2018-04-26

leetcode案例

/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
    if(nums.length==0)return 0;
    var i=0;
    for(var j=1;j<nums.length;j++){
        if(nums[j]!==nums[i]){
            i++;
            nums[i]=nums[j];
        }
    }
    return i+1;
};

执行时间:72ms

买卖股票的最佳时II

分析:第二个例子其实有点误导性,我们只要知道要获取最大利润,那么肯定是有利润就卖,所以只要后一天比前一天价格高,就计算收益。例如,[1,5,4,9]
利润最大方式:5-1 + 9-4 = 9。
其他方式:4-1 + 9-5 = 7;
9-1 = 8;

代码:

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
        // 声明利润变量
        var profit = 0;
        // 遍历数组
        for (var i = 0; i < prices.length; i++) {
            // 若前一项 < 后一项
            if (prices[i] < prices[i+1]) {
                // 计算差值,并累加利润
                profit += prices[i + 1] - prices[i];
           } 
        }
        return profit;
};

执行时间:76ms

完成日期:2018-04-27

leetcode案例

/**
 * @param {number[]} prices
 * @return {number}
 */

var maxProfit = function (prices) {
        var profit=0,
        len=prices.length;

        for(let i=0;i<len-1;++i){
        let now=prices[i],
            next=prices[i+1];
        if(next>now){
                profit+=next-now;
        }
    }
        return profit;
};

执行时间:64ms

旋转数组

分析一:最笨的办法,循环移动,每次将数组最后一个元素先保存到一个中间变量,所有元素依次向后移动一位,移动完k次后,将中间遍历的值赋到数组第一个元素。

代码:

var arrLen = nums.length;
    for (var i = 0; i < k; i ++){
        var lastVal = nums[nums.length - 1];  // 最后一个值存储到临时变量
        for (var j = nums.length - 2; j >= 0 ; j --){  // 所有元素依次后移一位
            nums[j + 1] = nums[j]; 
    }
    nums[0] = lastVal;  // 将临时变量的值赋给第一个元素

执行用时:388ms

分析二:分析一听上去就明显知道时间会很长,那么是否还有更好的办法,这就可以利用js数组两个动态添加删除的方法,直接移出数组末位,并添加到数组首位。而k相当于移动的次数。

代码:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function(nums, k) {

    for (var i = 0; i < k; i++) {
        nums.unshift(nums.pop());
    }

};

执行用时:164ms

分析三:在测试用例的过程中,我们会发现,如果k等于数组长度,是不需要移动的,这可以减少程序执行次数。所以我们应先判断k与数组长度关系,大于等于数组长度时稍微复杂一些,所以先讨论简单的k < 数组长度的情况。

  1. k < 数组长度
    1.1 计算移动完成后,数组末位索引
    1.2 使用splice从0开始到末位(包含末位)切割数组,并将切割得到的元素插入数组。

  2. k > 数组长度
    2.1 k 如果是数组长度的整数倍,则无需移动数组。
    2.2 k 如果不是数组长度的整数倍,移动次数为k/数组长度的余数即可。使用splice从0开始到末位(包含末位)切割数组,并将切割得到的元素插入数组。

代码:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function(nums, k) {
        var movedLastIndex;
        if (k < nums.length) {
            movedLastIndex = nums.length - k; // 此处为方便,故省略 - 1
        } else if (k >= nums.length && k % nums.length == 0) {
            // 不变
            return;
        } else if (k >= nums.length && k % nums.length != 0) {
            k = k % nums.length;
            movedLastIndex = nums.length - k;
        } 
        // 利用apply方法第二个形参可以传入数组的特性,将形参数组中的数值遍历传给push。
        nums.push.apply(nums,nums.splice(0, movedLastIndex));
};

执行用时:96ms

完成日期:2018-04-28

leetcode案例

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function(nums, k) {
        nums.unshift(...nums.splice(-k % nums.length));
};

执行用时:68ms

解析:移动次数/数组长度的余数就是向右移动次数的最小值,因为unshift方法可以接受多个参数,splice方法返回的是一个数组,通过扩展运算符 … 对应传到unshift方法,从而实现旋转。

存在重复

题目:给定一个整数数组,判断是否存在重复元素。如果任何值在数组中出现至少两次,函数应该返回 true。如果每个元素都不相同,则返回 false。

分析:ES6中引入了新的数据结构Set,类似数组,但是成员没有重复值,所以我们只需要将传入的数组传入Set中,对比Set的size和原数组的length是否一致就能判断数组是否存在重复值

代码:

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var containsDuplicate = function(nums) {
        let oldLen = nums.length;
        let setArr = new Set(nums);
        if (oldLen === setArr.size) {
            return false;
        } else {
            return true;
        }
};

执行时间:108ms

完成日期:2018-05-01

leetcode案例

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var containsDuplicate = function(nums) {
        let length = nums.length? nums.length : 0
        let set = new Set()
        nums.forEach((el) => {
            set.add(el)
        })
        if (set.size === length) {
            return false
        } else {
            return true
        }
};

执行时间:76ms

解析:显然,直接将数组以参数形式传入构造方法消耗的时间 > 通过遍历数组向Set集合添加元素的时间。

只出现一次的数字

分析一:先排序,数组会变成这些情况:[1,22,33,44],[11,22,3,44],[11,22,33,4],然后遍历数组:

  • 若为首个,与后一个元素不同
  • 若为末位,与前一个元素不同
  • 其他,与前后元素都不一直

代码:

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
        nums.sort();
        for (var i = 0; i < nums.length; i++) {
            switch (i) {
                case 0:
                    if (nums[i] != nums[i+1]) {
                        return nums[i];
                    }
                    break;
                case nums.length - 1:
                    if (nums[i] != nums[i-1]) {
                        return nums[i];
                    }
                    break;
                default:
                    if (nums[i] != nums[i-1] && nums[i] != nums[i+1]) {
                        return nums[i];
                    }
            }
        }
};

执行用时: 144ms

分析二:由于执行用时很慢,有强迫症的我便重新思考了一下其他的方法,执行时间长的很大原因是在排序上。是否不用排序能够实现呢。去查了一下,发现一个很巧妙的办法,就是用位运算中的按位亦或,一个整数和它本身异或之后得到值是0,0与其他整数异或得到的是这个整数本身,即:1 ^ 1 = 0, 1 ^ 0 = 1,2 ^ 0 = 2。

代码:

/**
* @param {number[]} nums
* @return {number}
*/
var singleNumber = function(nums) {
     var result = nums[0];
     for (var i = 1; i < nums.length; i++) {
          result ^= nums[i];
     }
     return result;
};

执行用时: 72ms

完成日期:2018-05-01

leetcode案例

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
      let result = 0;
      nums.forEach(el => (result ^= el));
      return result;
};

执行用时: 56ms

两个数组的交集II

分析一:先来最简单的,要找交集,分别遍历两个数组,进行对比,相同的即从数组中取出放入另一个数组即可。

代码:

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersect = function(nums1, nums2) {

        var result = [];

        for (var i = 0; i < nums1.length; i++) {
               for (var j = 0; j < nums2.length; j++) {
                    if (nums1[i] == nums2[j]) {
                // 放入结果数组
                        result.push(nums2[j]);
                // 从原数组中移除
                        nums2.splice(j,1);
                        break;
                    }
               } 
        }
        return result;
};

执行用时:96ms

分析二:很明显,这种办法消耗的时间比较长,时间复杂度O(n^2)。所以换一种思路,先遍历数组2,利用includes判断数组1中是否包含数组2的值,若包含,先将值添加到新数组中,然后把数组1中对应的值删除。

代码:

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersect = function(nums1, nums2) {
        /*
          遍历其中一个数组,并判断另一个数组中是否包含该数组的某元素
        */
        var result = [];

        for (var i = 0; i < nums2.length; i++) {
                if (nums1.includes(nums2[i])) {
                    result.push(nums2[i]);
                    nums1.splice(nums1.indexOf(nums2[i]),1);

                }
        }
        return result;
};

执行用时:84ms

完成日期:2018-05-02

leetcode案例

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersect = function(nums1, nums2) {
        const map={};
        const res=[];
        nums1.forEach(function (v) {
            map[v]?map[v]++:map[v]=1;
        });

        nums2.forEach(function (v) {
            if(map[v]){
                    res.push(v);
                    map[v]--;
            }
        });
        return res;
};

执行用时:56ms

分析:通过forEach遍历数组1并将数组1的元素作为map对象的key,而map对象的value其实是一个计数器,代表该元素存在的个数,存放到对象map中。接着遍历数组2,通过map[key]获取元素个数(其中key是数组2的元素)的方式,判断数组2的元素是否存在,存在则放入结果数组,并将计数器减1。

加一

分析:末位加一,显然末位为9时需要进1,所以分小于9大于等于9两种情况讨论,我们直接遍历数组,如果小于9直接加1返回数组即可,如果大于等于9则将该元素置为0,待遍历结束后,为数组首位加一个元素1即可。

代码:

/**
 * @param {number[]} digits
 * @return {number[]}
 */
var plusOne = function(digits) {

       for (var i = digits.length - 1; i >=0; i--) {
               if (digits[i] < 9) {
                   digits[i]++;
                   return digits;
               } else {
                   digits[i] = 0;
               }
       }
       // 若全为9
       digits.unshift(1);
       return digits;
};

执行用时:76ms

完成日期:2018-05-03

leetcode案例

/**
 * @param {number[]} digits
 * @return {number[]}
 */
var plusOne = function(digits) {
      var len = digits.length;
      for (var i = len - 1; i >= 0; i--) {
            if (digits[i] < 9) {
                  digits[i]++;
                  return digits; 
            }
            digits[i] = 0;
      }
      return [1, ...digits];
};

执行用时:52ms

移动零

题目:
给定一个数组 nums, 编写一个函数将所有 0 移动到它的末尾,同时保持非零元素的相对顺序。
例如, 定义 nums = [0, 1, 0, 3, 12],调用函数之后, nums 应为 [1, 3, 12, 0, 0]。
注意事项:
1.必须在原数组上操作,不要为一个新数组分配额外空间。
2.尽量减少操作总数

分析:由于要在同一个数组上操作,我们可以考虑多用一个指针index,index指向的元素都是不为0的,遍历数组,将不为0的数全放到数组前边,最后index之后的肯定都是0,只要在nums.length - index这长度遍历添加0即可。

代码:

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {

        var index = 0;
        for (var i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                    nums[index] = nums[i];
                    index++;
            } 

        }

        while(index < nums.length){
              nums[index] = 0;
              index++;
        }
};

执行用时:96ms

完成日期:2018-05-04

leetcode案例

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
        let pos = 0;
        for(let i = 0; i < nums.length ; i++) {
            if(nums[i] !== 0) {
                    nums[pos] = nums[i]
                    pos++
            }
        }
        for(; pos < nums.length; pos++) {
            nums[pos] = 0
        }

};

执行用时:64ms

两数之和

分析一:直观的想法(暴力搜索)就是从第一个数开始,逐一分别与后边元素相加,存在直接返回,不存在则从第二个数开始,逐一分别与后边元素相加,存在直接返回,不存在则继续之前操作,直到倒数第二个数与数组末位相加。可以看出,最坏情况则是遍历到倒数第二个数与数组末位相加。

代码:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {

        for (var i = 0; i < nums.length - 1; i++) {
            for (var j = i + 1; j < nums.length; j++) {
                    if (target == nums[i] + nums[j]) {
                          return [i, j];
                    }
            }    
        }

};

执行用时:160ms

分析二:明显分析一的做法耗时很长。于是乎,我想到了indexOf检索数组中元素,但是由于该方法只返回指定的字符串值在数组中首次出现的位置,故不合符需求。所以采用另一个办法,lastIndexOf方法,可返回一个指定的字符串值最后出现的位置。

代码:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {

        for (var i = 0; i < nums.length - 1; i++) {
            var searchVal = target - nums[i]; // 目标值
            var index = nums.lastIndexOf(searchVal); // 目标索引
            if (-1 != index && i != index) { // 存在
                    return [i, index];
            }
        }

};

执行用时:212ms

一口老血喷出来。。。没想到用时更加长了,我查了一下lastIndexOf的源码,发现其实它用了while循环,比分析一慢的原因是它每次检索都是全量检索的。

分析三:利用Js对象的特性,先声明一个temObj对象,key是数组值,value是数组下标。通过temObj[key]是否存在就可以判断数组是否存在该值。做法如下:

代码:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {

        var temObj = {};
        for (var i = 0; i < nums.length; i++) {
            var searchVal = target - nums[i]; // 要查找的值
            if (undefined != temObj[searchVal]) {
                return [temObj[searchVal], i]
            }
            // 将未通过判断的键值添加到temObj中
            temObj[nums[i]] = i;
       }

};

执行用时:76ms

完成日期:2018-05-06

leetcode案例

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
        let hashTable = {};
        for(let i = 0;i < nums.length;i ++){
            let complement = target - nums[i];
            if(hashTable.hasOwnProperty(complement)){
                return [hashTable[complement],i];
            }else{
                hashTable[nums[i]] = i;
            }
        }
        throw new Error('No two sum solutions');
};

执行用时:52ms

分析:此处思路通分析三类似,只不过用的检索方法是hasOwnProperty,这个方法可以用来检测一个对象是否含有特定的自身属性,该方法会忽略掉那些从原型链上继承到的属性。大家不要被执行时间迷惑便觉得hasOwnProperty比直接访问属性快,事实上是直接访问属性要比hasOwnProperty快。大家可以参考这个测试结果:Object hasOwnProperty vs direct 关于Jsperf测试Js代码性能的使用方法,我会另外发表一篇博客进行详细说明。

有效的数独

分析:这是我觉得稍微有点难的算法,写出来的感觉就像做数学题,其实就是找规律。首先,根据题目要求,提了三个条件,行和列都好计算,我们使用双重for循环来解决,行:board[i][j], 列:board[j][i]。而九宫格稍难,我们可将外层循环作为九宫格序号,内层循环作为格点序号,难点在于表示第i个九宫格第j个格点的坐标。

行号规律:
第0个九宫格:000111222; 第1个九宫格:000111222; 第2个九宫格:000111222;
第3个九宫格:333444555; 第4个九宫格:333444555; 第5个九宫格:333444555;
第6个九宫格:666777888; 第7个九宫格:666777888; 第8个九宫格:666777888;

每3个九宫格行号增3;对于1个九宫格,每3个格点行号增1。

所以第i个九宫格第j个格点的行号为:parseInt(i/3) * 3 + parseInt(j/3)
parseInt(i/3) * 3 对应 每3个九宫格增加3,不足三个算0个(向下取整)
parseInt(j/3) 对应 每3个格点加1,不足三个算0个(向下取整)

列号规律:
第0个九宫格:012012012; 第1个九宫格:345345345; 第2个九宫格:678678678;
第3个九宫格:012012012; 第4个九宫格:345345345; 第5个九宫格:678678678;
第6个九宫格:012012012; 第7个九宫格:345345345; 第8个九宫格:678678678;

对于下一个九宫格列号增3,循环周期为3;对于1个九宫格,每下一个格点列号增1,周期也为3。

所以第i个九宫格第j个格点的列号为:(i % 3) * 3 + (j % 3) * 1
(i % 3) * 3 = 0,3,6,(j % 3) * 1 = 0,1,2(数组下标从0开始)

代码:

/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function(board) {
    /*
      ES6新增数据结构Set,类似于数组,但是成员的值都是唯一的,没有重复的值。
      它的一些api在此处符合使用场景。
      我们可以这样表示 行:board[i][j], 列:board[j][i]
    */
    let rolSet = new Set(); // 行
    let colSet = new Set(); // 列
    let boardSet = new Set(); // 块

    for (let i = 0; i < 9; i++) {

        rolSet.clear();
        colSet.clear();
        boardSet.clear();

        for (let j = 0; j < 9; j++) {

            if ("." != board[i][j]) {
                // 如果行中包含相同元素
                if (rolSet.has(board[i][j])) {
                    return false
                } else {
                   rolSet.add(board[i][j]); 
                }
            }

            if ("." != board[j][i]) {
                // 如果列中包含相同元素
                if (colSet.has(board[j][i])) {
                    return false
                } else {
                   colSet.add(board[j][i]); 
                } 
            }

            // 行号+偏移量  
            var RowIndex = 3 * parseInt(i / 3) + parseInt(j / 3);  
            // 列号+偏移量  
            var ColIndex = 3 * (i % 3) + j % 3;

            if ("." != board[RowIndex][ColIndex]) {
                if (boardSet.has(board[RowIndex][ColIndex])) {
                    return false
                } else {
                    boardSet.add(board[RowIndex][ColIndex]);
                }

            }

        }
    }
    return true;

};

执行用时:124ms

完成日期:2018-05-07

leetcode案例

/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function(board) {
  var a = new Array(9),
      b = new Array(9),
      c = new Array(9);

  for (var k = 0; k < 9; k++) {
      a[k] = new Array(9);
      b[k] = new Array(9);
      c[k] = new Array(9);
  }

  for (var i = 0; i < 9; i++) {
    for (var j = 0; j < 9; j++) {
      var temp = board[i][j];
      if (temp === ".") {
        continue;
      }
      if (a[i][temp - 1] || b[temp - 1][j] || c[Math.floor(i / 3) * 3 + Math.floor(j / 3)][temp - 1]) {
        return false;
      }
      a[i][temp - 1] = b[temp - 1][j] = c[Math.floor(i / 3) * 3 + Math.floor(j / 3)][temp - 1] = true;
    }
  }
  return true;
};

执行用时:76ms

分析:查了相关资料,该案例使用的是位图法,我们根据题目不难发现,在数独中可能出现的数字范围是1-9,所以它可以对应数组下标0-8,因此可以通过数组下标来存储元素。因此,一开始便创建了三个数组分别对应行、列、九宫格,数组结构如下:

可以看到,每个数组有9个元素,每个元素是一个长度为9的空数组。a代表行,b代表列,c代表九宫格。接着双重循环,将board中的值存储于数组下标,并将值置为true。例:

a[0][n]表示board第一行所有元素(除.外),board第一行所有元素的值(除.外)为 n+1 (0 < n < 9)。a[0][n] = true 则表示 board 第一行的值中已存在重复的 n;

b数组同理。

c,之前我们提到过 Math.floor(i / 3) * 3 + Math.floor(j / 3) 表示第i个九宫格的第j个元素格点的行号,第0个九宫格行号:000 111 222 共9个,所以c[0][n1],c[0][n2],c[0][n3],c[1][m1],c[1][m2],c[1][m3],c[2][p1],c[2][p2],c[2][p3],代表第0个九宫格的元素。

旋转图像

分析:线性代数里有个矩阵叫转置矩阵,例:

1 2 3      1 4 7
4 5 6  =>  2 5 8
7 8 9      3 6 9

其实就是将一个矩阵行列互换。之所以提这个概念,是因为若我们将这个转置矩阵每行逆序会变成这样:

7 4 1
8 5 2
9 6 3

这就是我们希望得到的顺时针旋转90°的矩阵。求转置矩阵有个特点,对角线上的数不变。
2阶矩阵:第1行交换1次(列:2),第2行交换0次;
3阶矩阵:第1行交换2次(列:2,3),第2行交换1次(列:3);第3行交换0次;
4阶矩阵:第1行交换3次(列:2,3,4),第2行交换2次(列:3,4);第3行交换1次;第4行交换0次。
n阶矩阵:第1行交换n-1次(列:2,3,4 … n),第2行交换n-2次(列:3,4 … n) … 第n行交换0次;

外层循环表示行,内层循环表示列。j的初始值 = i + 1;

代码:

/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var rotate = function(matrix) {
    // 行:matrix[i][j]  列:matrix[j][i]
    // 先求转置矩阵 matrix
    for (let i = 0; i < matrix.length; i++) {
        for (let j = i + 1; j < matrix.length; j++) {
            var temp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = temp;
        }
        matrix[i].reverse();
    }
};

执行用时:68ms

求旋转矩阵的方法不止这一个,有兴趣的同学可以去翻翻同济版的线性代数,深入研究可以看高等代数。大学曾经深入学过,奈何如今只记得书本名。(- -||)

完成日期:2018-05-08

leetcode案例

/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var rotate = function(matrix) {
  var n = matrix.length;
  var i, j;
  for (i = 0; i < Math.floor(n / 2); i++) {
    for (j = 0; j < n; j++) {
      [matrix[i][j], matrix[n - 1 - i][j]] = [matrix[n - 1 - i][j], matrix[i][j]];
    }
  }
  for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++) {
      if (i >= j) continue;
      [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
    }
  }    
};

执行用时:64ms

分析:作者思路是先首行和尾行互换,再对矩阵进行转置。例:

1 2 3      7 8 9      7 4 1
4 5 6  =>  4 5 6  =>  8 5 2 
7 8 9      1 2 3      9 6 3

总结

不知不觉已经把初级数组的题刷完了,收获挺大的,有时候写算法题更像是在写数学题,答案不唯一,但是总会有更巧妙的解法,在寻找答案思考过程中,又不断地熟悉了一遍Js数组的api,也用了一些ES6里的东西。这篇博客相当于个人的读书笔记,只为在遇到问题的时候,能快速的将忘却的知识迅速捡起来,而不会像刚才一样,只记得概念,具体的东西还要查半天资料,再通过一系列头脑风暴,然后回忆起来。