常见数据结构与算法-JavaScript版(二)

269 阅读7分钟

4.队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

队列的数据结构在JavaScript中可以表示为:

    //队列的构造函数
    function Queue() {
        this.items = [];
        this.length = this.itmes.length;
        this.front = null;
    }
    //向队列尾插入一个元素的方法enqueue
    Queue.prototype.enqueue = function (ele) {
        this.items.push(ele);
        this.front = this.items[0];
    }
    //向队列头删除一个元素的方法dequeue 
    Queue.prototype.dequeue = function (ele) {
        this.items.shift(ele);
        this.front = this.itmes.length ? this.itmes[0] : null;
    }

JavaScript中没有内置的队列结构,一般用Array来实现,Array的shift和push操作能很好的完成队列的出队和入队操作。length和isEmpty等根据Array的length来判断即可实现。队列在leetcode的联系题也基本是用array来实现队列,不再展开。

5.字符串

字符串遍历时可以自前向后,也可以自后向前。利用这一点可以轻松解决反转字符串验证回文字符串。再加上字符串和数字类型转换的知识,即可解决整数反转字符串转换整数

(1).字符串中的第一个唯一字符

//给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
/**
 * @param {string} s
 * @return {number}
 */
var firstUniqChar = function(s) {
    for(let i=0;i<s.length;i++){
        //若字符串自前向后遍历得到的索引与自后向前遍历得到的索引相同,则字符唯一
        if(s.indexOf(s[i])==s.lastIndexOf(s[i])){
            return i;
        }
    }
    return -1;
};

(2).有效的字母异位词

//给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
    /**
     * @param {string} s
     * @param {string} t
     * @return {boolean}
     */
    var isAnagram = function (s, t) {
        function sortString(str) {
            let arr = [];
            //for-of循环可以遍历unicode大于0XFFFFFF的情况
            for (let i of str) {
                arr.push(i);
            }
            //利用数组的排序方法,优于冒泡排序
            arr.sort();
            //将数组转换为字符串方便判断相等,Array类型不能直接判断相等
            return arr.join("");
        }
        return sortString(s)== sortString(t);
    }

如果不对字符串进行排序的话,也可以选择利用嵌套循环,两个字符串中的字符相等时,将字符串中的字符删除,这样可以避免字母重复对判断相等造成的影响。

(3).实现 strStr()

//给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 
//字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1
    var strStr = function (haystack, needle) {
        let len = needle.length;
        if(len<=0) return 0;
        for(let i=0;i<haystack.length-len;i++){
            //将needle看作一个整体,haystack截取相同长度字符串,判断相等
            let s = haystack.slice(i,i+len);
            if(s===needle) return i;
        }
        return -1;
    }

也可以利用一个while循环

var strStr = function(haystack, needle) {
    if(needle ==="") return 0;
    let i=0;
    let j=0;
    while(i<haystack.length&&j<needle.length){
        if(haystack[i] === needle[j]){
            i++;
            j++;
        }else{
            i = i-j+1;
            j = 0;
        }
    }
    if(j==needle.length) return i-j;
    return -1;
};

(4).外观数列

//「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
    /**
     * @param {number} n
     * @return {string}
     */
    var countAndSay = function (n) {
        if (n == 1) {
            return "1";
        } else {
            return countNum(countAndSay(n - 1));
        }
        function countNum(str) {
            str = str + "/";
            //num用来记录数字
            let num = [];
            //count用来记录数字出现的次数
            let count = [];
            let temp = 1;
            let result = "";
            for (let i = 0; i < str.length - 1; i++) {
                if (str[i] !== str[i + 1]) {
                    num.push(str[i]);
                    count.push(temp);
                    temp = 1;
                } else {
                    temp++;
                }
            }
            for (let j = 0; j < num.length; j++) {
                result += count[j] + num[j];
            }
            return result;
        }
    };

(5).最长公共前缀

//编写一个函数来查找字符串数组中的最长公共前缀。
//如果不存在公共前缀,返回空字符串 ""。
/**
 * @param {string[]} strs
 * @return {string}
 */
var longestCommonPrefix = function (strs) {
        if(strs.length==0) return "";
        //以第一个字符串的长度作为公共前缀
        let size = strs[0].length;
        let pub = "";
        for (let i = 0; i < size; i++) {
            pub += strs[0][i];
            for (let j = 1; j < strs.length; j++) {
                //若存在字符串为空,直接返回空字符串
                if (strs[j] === "") return "";
                //若存在字符串长度小于第一个字符串长度,改变size,减少循环次数
                if (strs[j].length < size) size = strs[j].length;
                if (strs[j][i] !== strs[0][i]) {
                    pub = pub.slice(0, pub.length - 1);
                    return pub;
                }
            }
        }
        return pub;
    };

6.数组

利用数组的splice方法即可轻松解决从排序数组中删除重复项旋转数组移动零问题。数组先排序再遍历即可解决存在重复只出现一次的数字问题。双层的遍历循环即可解决两个数组的交集 II两数之和问题。

使用线性时间复杂度来解决只出现一次的数字可以用异或操作解决,相同的数字异或结果为0,0和任意数字异或的结果为数字本身。

(1).买卖股票的最佳时机 II

//给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
//设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易,多次买卖一支股票。
/**
 * @param {number[]} prices
 * @return {number}
 */
    var maxProfit = function (prices) {
        let result = 0;
        if (prices.length <= 1) return result;
        for (let i = 0; i <= prices.length - 2; i++) {
            if (prices[i + 1] - prices[i] > 0) {
                result = result + prices[i + 1] - prices[i];
            }
        }
        return result;
    };

只要后一天的价格大于前一天就进行一次交易,从数组[1,2,3,4,5]得到的结果可以知道,不论是(2-1)+(3-2)+(4-3)+(5-4)还是(5-1)得到结果并没有不同。

(2).有效的数独

//判断一个 9x9 的数独是否有效。
/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function (board) {
        let valid = true;
        //数组先排序,再判断是否存在重复
        function exist(arr) {
            let tempArr = [];
            for (let i = 0; i < arr.length; i++) {
                if (Number(arr[i])) {
                    tempArr.push(Number(arr[i]));
                }
            }
            tempArr.sort();
            for (let w = 0; w < tempArr.length - 1; w++) {
                if (tempArr[w] == tempArr[w + 1]) {
                    return false;
                }
            }
            return true;;
        }
        for (let j = 0; j < 9; j++) {
            //数独的每一行是否有效
            valid = valid && exist(board[j]);
            let a = [];
            //数独的每一列是否有效
            for (let k = 0; k < 9; k++) {
                a.push(board[k][j]);
            }
            valid = valid && exist(a);
        }
        //数独的每个小九宫格是否有效
        for (let x = 0; x < 3; x++) {
            let b = [];
            let c = [];
            let d = [];
            for (let y = 3 * x; y < 3 * x + 3; y++) {
                for (let z = 0; z < 3; z++) {
                    b.push(board[y][z]);
                    c.push(board[y][z + 3]);
                    d.push(board[y][z + 6]);
                }
            }
            valid = valid && exist(b);
            valid = valid && exist(c);
            valid = valid && exist(d);
        }
        return valid;
    };

(3).旋转图像

//给定一个 n × n 的二维矩阵表示一个图像。
//将图像顺时针旋转 90 度。
 /**
    * @param {number[][]} matrix
    * @return {void} Do not return anything, modify matrix in-place instead.
    */
    var rotate = function (matrix) {
        let len = matrix.length - 1;
        //i,j为第一次交换的起始位置,x,y为要旋转的元素的下标
        function swap(i, j, temp, x, y) {
            //如果旋转回起始位置,则跳出循环
            if (x == i && y == j) {
                return;
            } else {
                // 矩阵A中位置[i,j]处的数据,旋转后应该处于[j, n-i-1],构成一个循环
                let a = y < 0 ? j : y;
                let b = x < 0 ? len - i : len - x;
                let t = matrix[a][b];
                matrix[a][b] = temp;
                swap(i, j, t, a, b);
            }
        }
        for (let i = 0; i < len; i++) {
            for (let j = i; j < len - i; j++) {
                swap(i, j, matrix[i][j], -1, -1);
            }
        }
    };

首先要清楚矩阵转换时的关系,是将[i][j]位置的元素旋转至[j][n-i-1].以上这样的解法时间复杂度为O(n3)。在网上看到将矩阵转换为json深拷贝一份数组再移动位置的解法,非常巧妙,通过O(n2)时间复杂度即可解决。

var rotate = function(matrix) {
    let n = matrix.length
    let mm = JSON.parse(JSON.stringify(matrix))
    //矩阵顺指针旋转
    for(let i = 0; i < n; i++) {
        for(let j = 0; j < n; j++) {
            matrix[i][j] = mm[n-1-j][i]
        }
    }
    
    return matrix
};