数据与结构练习

182 阅读6分钟

接雨水

仔细分析下来发现接多少雨水的关键是墙,如果没有墙壁,水就蓄不起来。

所以对于每个柱子它能蓄多少水,要看它的左边最高的柱子和它右边最高的柱子,取这两者的较小值来作为当前柱子的蓄水能力值。

如果两者的较小值小于等于当前柱子的高度,说明当前柱子它蓄不了水,否则它的蓄水能力就是这个较小值减去当前柱子的高度。

var trap = function(height) {
    const left = new Array(height.length).fill(0)
    const right = new Array(height.length).fill(0)

    let max = height[0]
    for (let i = 1; i < height.length; i ++) {
        left[i] = max
        max = Math.max(max, height[i])
    }
    max = height[height.length - 1]
    for (let i = height.length - 2; i >= 0; i --) {
        right[i] = max
        max = Math.max(max, height[i])
    }

    let ans = 0
    for (let i = 1; i < height.length - 1; i ++) {
        const cur = height[i]
        const m = Math.min(left[i], right[i])
        if (m > cur) {
            ans += (m - cur)
        }
    }
    return ans
};

作者:scnu_evan
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/javascript-42-jie-yu-shui-by-scnu_evan-u0nu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

作者:scnu_evan 链接:leetcode-cn.com/problems/tr… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

接雨水2

股票买卖

//动态规划  
function maxProfit(prices: number[]): number {
    let minPrice = Number.MAX_VALUE;
    let maxProfit = 0;
    //前i天的最大收益 = max{前i-1天的最大收益,第i天的价格 - 前i-1天中的最小价格}
    for (let i of prices) {
        minPrice = Math.min(minPrice, i);
        maxProfit = Math.max(maxProfit, i - minPrice);
    }
    return maxProfit; 
};

股票买卖2

function maxProfit(prices: number[]): number {
    //极端情况处理
    if (prices === null || prices.length === 1) {
        return 0;
    }
    
    let benefit = 0;
    //只要后一天的价格比前一天高就可以计入总利润
    for (let i = 0; i < prices.length - 1; i++) {
        let j = i + 1;
        if (prices[i] < prices[j]) {
            benefit = prices[j] - prices[i] + benefit;
        }
    }

    return benefit;
};

零钱兑换

function coinChange(coins: number[], amount: number): number {  
    //初始化  amount + 1的数组,使金额amount按照正常数值变化
    const dp = new Array(amount + 1).fill(Number.POSITIVE_INFINITY);
    dp[0] = 0;//corner case dp[0] 表示金额为0时,与0中符合

    for (let currentAmount = 1; currentAmount <= amount; currentAmount++) {
        for (let currentCoin of coins) {
            //减掉当前硬币值后的剩余总金额leftAmount
            const leftAmount = currentAmount - currentCoin;
            //剩余金额小于零就不需要计算了,说明凑不出来
            if (leftAmount < 0 || dp[leftAmount] === Number.POSITIVE_INFINITY) continue;
            //如果剩余金额的dp也凑不出来,说明当前页凑不出来
            if (dp[leftAmount] === Number.POSITIVE_INFINITY) continue; 
                
            // if (leftAmount < 0 || dp[leftAmount] === Number.POSITIVE_INFINITY) continue;  

            // dp[leftAmount] + 1;说明能凑出来+1 就是加currentCoin 这个硬币一次      
            dp[currentAmount] = Math.min(dp[leftAmount] + 1, dp[currentAmount]);
        }
    }
    //有凑不出来的情况所以要拿出来判断下
    return dp[amount] === Number.POSITIVE_INFINITY ? -1:dp[amount];
};

零钱兑换2

//动态规划
function change(amount: number, coins: number[]): number {
    let dp = new Array(amount + 1).fill(0);
    dp[0] = 1;
    for (let coin of coins) {
        for(let currentAmout = coin; currentAmout <= amount; currentAmout++) {
            //剩余总量
            const leftAmout = currentAmout - coin;

            dp[currentAmout] = dp[currentAmout] + dp[leftAmout];
        }
    }
    return dp[amount];
};

最大子数组和

//动态规划
function maxSubArray(nums: number[]): number {
    for (let i = 1; i < nums.length; i++) {
        if (nums[i - 1]  > 0) {
            nums[i] = nums[i] + nums[i - 1]; 
        }
    }
    return Math.max(...nums);
};

排序数组

  • 冒泡
function sortArray(nums: number[]): number[] {
    for (let i = 0; i < nums.length - 1; i++) {
        for (let j = i + 1; j < nums.length; j++) {
            if (nums[j] < nums[i]) {
                const temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
    }
    return nums;
};
  • 插入排序
function sortArray(nums: number[]): number[] {
    //从第二个开始排序
    for (let i = 1; i < nums.length; i++) {
        for (let j = 0; j < i; j++) {
            if (nums[j] > nums[i]) {
                const temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
    }
    return nums;
};
  • 快排
function sortArray(nums: number[]): number[] {
    quickSort(nums,0, nums.length - 1);
    return nums; 
};

function quickSort(nums: number[], start: number, end: number) {
    if (start >= end) {
        return;
    }

    const pivot = nums[start];
    let left = start;
    let right = end;

    while (left < right) {
        while (left < right && nums[right] >= pivot) {
            right--;
        }
        //上面的循环跳出来就说明当前 nums[right] < pivot, 那就交换到左边坑位
        if (left < right && nums[right] < pivot) {
            nums[left] = nums[right];
        }

        while (left < right && nums[left] <= pivot) {
            left++;
        }

        //那就交换到右边坑位
        if (left < right && nums[left] > pivot) {
            nums[right] = nums[left];
        }
        //结束了pivot插入到中间空位
        if (left >= right) {
            nums[left] = pivot;
        }
    }

    quickSort(nums, start, left-1);
    quickSort(nums, left+1, end);
}

下面这题的题目被勿删了。。。

解法一:利用Map不可重复性,比较map.size和string.lenght

TypeScript
function isUnique(astr: string): boolean {
    // const stringArray = astr.split('');
    let stringMap = new Map();
    
    for (let i = 0; i < astr.length; i++) {
        stringMap.set(astr.substr(i,1), i);
    }
    if (astr.length === stringMap.size) {
        return true;
    } else {
        return false;
    }
};

解法二:遍历判断map是否存在对应的value

TypeScript
function isUnique(astr: string): boolean {
    let stringMap = new Map();
    for (let i = 0; i < astr.length; i++) {
        const p = astr.substr(i,1);
        if (stringMap.has(p)) {
            return false;
        }
        stringMap.set(p, i);
    }
    return true
};

两数之和

解法一:双层遍历相加判断

TypeScript
function twoSum(nums: number[], target: number): number[] {
    let p: number[] = [];
    for ( let i = 0 ; i < nums.length; i++ ) {
       let p1 = nums[i];
       for (let j = i + 1; j < nums.length; j++) {
           const sum = p1 + nums[j];
           if (sum === target) {
               p = [i, j];
               return p;
           }
       }
    }
    return p;
};

解法二:利用hashMap

TypeScript
function twoSum(nums: number[], target: number): number[] {
    let myMap = new Map();
    //key : num, value: index
    myMap.set(nums[0],0);
    //从1开始
    for (let i = 1; i < nums.length; i++) {
        const t = target - nums[i];
        if  (myMap.has(target - nums[i])) {
            return [myMap.get(target - nums[i]), i];
        }
        myMap.set(nums[i], i);
    }
    return [];
};

字符翻转

TypeScript
function reserveStr(str: string) {
     const tempStrArr = str.split('');
     let tmpStr = '';
     console.log(tempStrArr);
     for (let i = str.length - 1; i >= 0; i--) {
         tmpStr = tmpStr + str.substr(i,1);
     }
     console.log('tmpStr' + tmpStr);
     return tmpStr;
}

两数相加 TS

TypeScript
function addTwoNumbers(l1: ListNode | null, l2: ListNode | null): ListNode | null {
    let p = 0;
    let carry = 0;// 进位
    const sum = l1.val + l2.val + carry;
    console.log('sum: ' + sum);
    p = sum % 10;
    console.log('p: ' + p);
    carry = Math.floor(sum/10);
    const returnNode = new ListNode(p,null);
    let tempNode = returnNode;
    while (l1.next && l2.next) {
        l1 = l1.next;
        l2 = l2.next;
        const sum = l1.val + l2.val + carry;
        console.log('sum: ' + sum);
        p = sum % 10;
        console.log('p: ' + p);
        carry = Math.floor(sum/10);   
        console.log('carry: ' + carry); 
        tempNode.next = new ListNode(p,null);
        tempNode = tempNode.next;
    }

    if (l1.next) {// l1.lenght > l2.lenght
        while (l1.next) {
            l1 = l1.next;  
            const sum = l1.val + carry;
            p = sum % 10;
            carry = Math.floor(sum/10);
            tempNode.next = new ListNode(p,null);
            tempNode = tempNode.next;
        }
    }

    if (l2.next) {// l2.lenght > l1.lenght
        while (l2.next) {
            l2 = l2.next;
            const sum = l2.val + carry;
            p = sum % 10;
            carry = Math.floor(sum/10);
            tempNode.next = new ListNode(p,null);
            tempNode = tempNode.next;
        }
    }

    if (carry > 0) {//如果最后相加还有进位,就应该新建这个进位的节点
        tempNode.next = new ListNode(carry,null);
    }

    return returnNode;
};

Stack

export class Stack<T> {
    private elements: T[];
    size: number;

    constructor() {
        this.elements = [];
        this.size = 0;
    }

    push(item: T) {
        this.elements.push(item);
        this.size++;
    }

    pop() {
        if (this.size > 0) {
            this.elements.pop();
            this.size--;
        }
    }

    top(): T | null {
        if (this.size === 0) {
            return null;
        } else {
            return this.elements[this.size - 1];
        }
    }
}

export function isValid(s: string): boolean {
    if (s.length === 0) {
        return true;
    }

    const stack = new Stack();
    for (let i = 0; i < s.length; i++) {
        const p = s.substr(i, 1);
        if (p === '(' || p === '{' || p === '[') {
            stack.push(p)
        } else {
            if (p === ')' && stack.top() === '(') {
                stack.pop();
                continue;
            } else if (p === '}' && stack.top() === '{') {
                stack.pop();
                continue;
            } else if (p === ']' && stack.top() === '[') {
                stack.pop();
                continue;
            } else {
                stack.push(p);
            }
        }
    }

    if (stack.size === 0) {
        return true;
    } else {
        return false;
    }
};

Queue

export class Queue<T> {
    private elements: T[];

    constructor() {
        this.elements = [];
    }

    push(item: T): void {
        this.elements.push(item);
    }

    pop(): T | null {
        if (this.elements.length === 0) {
            return null;
        }
        const item = this.elements[0];
        this.elements = this.elements.slice(1, this.elements.length);
        return item;
    }

    top(): T | null {
        if (this.elements.length === 0) {
            return null;
        }
        const item = this.elements[0];
        return item;
    }

    isEmpty(): boolean {
        if (this.elements.length === 0) {
            return true;
        } else {
            return false
        }
    }
}

export function queueTest() {
    const queue = new Queue();
    console.log('isEmpty:' + queue.isEmpty()); //isEmpty:true
    queue.push(1);
    queue.push(2);
    console.log('isEmpty:' + queue.isEmpty());   //isEmpty:false
    console.log('queueTop:' + queue.top());      //queueTop:1
    console.log('queuePop:' + queue.pop());      //queuePop:1
    console.log('queuePop:' + queue.pop());      //queuePop:2
    console.log('isEmpty:' + queue.isEmpty());   //isEmpty:true
    console.log('queuePop:' + queue.pop());      //queuePop:null
}

链表合并

  • 暴力 时间复杂度 O(list1.length + list2.length)

空间复杂度 O(1)


function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null {   
    //使用临时节点
    let tempNode = new ListNode(-1);
    //最终返回的ListNode
    const backNode = tempNode;
    
    while (list1 && list2) {
        if (list1.val < list2.val) {
            tempNode.next = list1;
            list1 = list1.next;
            tempNode = tempNode.next;
        } else {
            tempNode.next = list2;
            list2 = list2.next;
            tempNode = tempNode.next;
        }
    }

    if (list2) {
        tempNode.next = list2
    }

    if (list1) {
        tempNode.next = list1
    }
    //注意这里返回的是 backNode.next,因为backNode.next才是第一元素的开始
    return backNode.next;
};

  • 递归 时间复杂度 O(list1.length + list2.length)

空间复杂度 O(list1.length + list2.length)

function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null {
    if (list1 === null) {
        return list2;
    }
    if  (list2 === null) {
        return list1;
    }
    if (list1.val <= list2.val) {
        list1.next = mergeTwoLists(list1.next,list2);
        return list1;
    } else {
        list2.next = mergeTwoLists(list1,list2.next);
        return list2;
    }
};

罗马数字转整数

时间复杂度 O(n)

空间复杂度 O(1)

function romanToInt(s: string): number {
    //先建立map
    const vauleMap = new Map();

    vauleMap.set('I',1);
    vauleMap.set('V',5);
    vauleMap.set('X',10);
    vauleMap.set('L',50);
    vauleMap.set('C',100);
    vauleMap.set('D',500);
    vauleMap.set('M',1000);
    vauleMap.set('IV',4);
    vauleMap.set('IX',9);
    vauleMap.set('XL',40);
    vauleMap.set('XC',90);
    vauleMap.set('CD',400);
    vauleMap.set('CM',900);

    let value: number = 0;
    for (let i = 0; i < s.length; i++) {
        const p = s.charAt(i);
        if (p === 'I' || 'X' || 'C') {
            //判断是否到最后了
            if (i === s.length - 1) {
                value = value + vauleMap.get(p);
            } else {
                //判断下一个字符是否和当前字符组成复合数字
                const nextP = s.substr(i, 2);
                if (vauleMap.get(nextP)) {
                    value = value + vauleMap.get(nextP);
                    i++; //位移两位
                } else {
                   value = value + vauleMap.get(p);
                }
            }
        } else {
            value = value + vauleMap.get(p);
        }
    }
    return value;
};

无重复字符的最长子串

  • 暴力解法: 遍历取出子串(O(n^2)),判断是否存在相同子串(O(n)); 时间复杂度 O(n^3)
//是否包含重复字符串
function isContainSameChar(s: string): boolean {
    const tempMap = new Map();
    for (let i = 0; i < s.length; i++) {
        const c = s.charAt(i);
        if (tempMap.get(c)) {
            return true;
        } else {
            tempMap.set(c, 1);
        }
    }
    return false;
}

function lengthOfLongestSubstring(s: string): number {
    const strMap = new Map();
    let maxStr = '';
    for (let i = 0; i < s.length; i++) {
        for (let j = i + 1 + maxStr.length; j < s.length + 1; j ++) {
            const p = s.substring(i,j);
            if (p.length > maxStr.length) {
                const r = isContainSameChar(p);
            console.log('是否有重复字母' + p + ':' + r )
                if (!r) {
                    if (maxStr.length < p.length) {
                        maxStr = p;
                    }
                } 
            }
        }
    }

    return maxStr.length;
};
  • 滑动窗口: hashmap + 滑动窗口法 时间复杂度 O(n)
function lengthOfLongestSubstring(s: string): number {
    const strMap = new Map();
    let maxLength = 0;
    let i = 0;//滑动窗口右边界
    let j = 0;//滑动窗口左边界
    for (i; i < s.length; i++) {
        //当前元素不在set中 就加入set 然后更新最大长度,i++继续下一轮循环
        if (!strMap.has(s[i])) {
            strMap.set(s[i], true);
            if (maxLength < strMap.size) {
               maxLength = strMap.size;
            }
        } else {
            //set中有重复元素不断让j++ 并删除窗口之外的元素 直到滑动窗口内没有重复的元素
            while (strMap.has(s[i])) {
                strMap.delete(s[j]);
                j++;
            }
            strMap.set(s[i],true);
        }
    }
    return maxLength;
};

寻找两个正序数组的中位数

  • 暴力解法 时间复杂度O(m + n)
function findMedianSortedArrays(nums1: number[], nums2: number[]): number {
    //合并成一个新的数组
    const tArray: number[] = [];
    while (nums1.length > 0 || nums2.length > 0) {
        if (nums1.length === 0 && nums2.length > 0) {
            tArray.push(nums2[0]);
            const p = nums2.shift();
            console.log('popZ' + p);
        }

        if (nums2.length === 0 && nums1.length > 0) {
            tArray.push(nums1[0]);
            const p = nums1.shift();
            console.log('popK' + p);
        }

        if (nums1.length > 0 && nums2.length > 0) {
            if (nums1[0] > nums2[0]) {
                tArray.push(nums2[0]);
                const p = nums2.shift();
                console.log('popY' + p);
            } else {
                tArray.push(nums1[0]);
                const p = nums1.shift();
                console.log('popX' + p);
            }
        }
    }
    console.log(tArray);

    //是数组是奇数
    if (tArray.length % 2 > 0) {
        // 1 2 3 4 5 
        // 0 1 2 3 4
        const index = Math.floor(tArray.length/2);//向下取整
        console.log('index' + index);

        return tArray[index];
    } else {//是偶数
        // 1 2 3 4 5 6
        // 0 1 2 3 4 5

       const p1 = tArray[tArray.length/2 -1];
       const p2 = tArray[tArray.length/2];
       return (p1 + p2)/2;
    }
};
  • 二分查找法 。。。

动态规划

青蛙跳台阶

问题分解 0 -> 0

1 -> 1

2 -> 11 2

3 -> 111 12 21

4 -> 1111 22 121 112 211

5 -> 11111 2111 1211 1121 1112 221 212 122

  • 时间复杂度 O(n)
  • 空间复杂度 O(1);
function levelWays(level: number) : number {
    let ways = 0;

    if (level === 0) {
        return 0;
    }

    if (level === 1) {
        return 1;
    }

    if  (level === 2) {
        return 2;
    }

    const myMap = new Map();
    myMap.set(1, 1);
    myMap.set(2, 2);
    for (let i = 3; i < level + 1; i ++) {
        const pWays = myMap.get(i - 1) + myMap.get(i - 2);
        myMap.set(i, pWays);
    }

    return myMap.get(level);
}

console.log(levelWays(1)); // 1
console.log(levelWays(2)); // 2
console.log(levelWays(3)); // 3
console.log(levelWays(4)); // 5
console.log(levelWays(5)); // 8

不同路径

  • 时间复杂度 O(m*n)

  • 空间复杂度 O(1);

  • 使用map实现

function uniquePaths(m: number, n: number): number {
    //边界判断
    if (m === 0 && n === 0) {
        return 0;
    }

    if (n === 0 && m > 0) {
        return 1;
    }

    if (m === 0 && n > 0) {
        return 1;
    }

    const dpMap = new Map();
    dpMap.set('0-0',0);
    
    //某项为0,那么永远只有一种走法
    for (let i = 0; i <= n; i++) {
        dpMap.set(`0-${i}`,1);
    }
    for (let i = 0; i <= m; i++) {
       dpMap.set(`${i}-0`,1);
    }
    console.log(dpMap);
    
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            //最小重复结构
            //(x,y) => 上一步要么来自(x-1,y),要么来自(x,y-1)
            //即 dpPath[x][y] = dpPath[x-1][y] + dpPath[x][y-1];
            console.log(`${i-1}-${j}: ` + dpMap.get(`${i-1}-${j}`));
            console.log(`${i}-${j-1}: ` + dpMap.get(`${i}-${j-1}`));
            dpMap.set(`${i}-${j}`, dpMap.get(`${i-1}-${j}`) + dpMap.get(`${i}-${j-1}`));
            console.log(`${i}-${j}: ` + dpMap.get(`${i}-${j}`));
        }
    }
    return dpMap.get(`${m-1}-${n-1}`);
};
  • 使用二维数组实现
    function uniquePath(pointX: number, pointY: number): number {
        //边界判断
        if (pointX === 0 && pointY === 0) {
            return 0;
        }

        if (pointY === 0 && pointX > 0) {
            return 1;
        }

        if (pointX === 0 && pointY > 0) {
            return 1;
        }

        //创建内部全为0的数组
        const dpPath = create2DArray(pointX, pointY);
        console.log(dpPath);
        //边界点
        dpPath[0][0] = 0;

        //如果在
        for (let i = 1; i < pointY; i++) {
            dpPath[0][i] = 1;
        }

        for (let i = 1; i < pointX; i++) {
            dpPath[i][0] = 1;
        }
        console.log(dpPath);

        //最小重复结构
        //(x,y) => 上一步要么来自(x-1,y),要么来自(x,y-1)
        //即 dpPath[x][y] = dpPath[x-1][y] + dpPath[x][y-1];
        for (let i = 1; i < pointX; i++) {
            for (let j = 1; j < pointY; j++) {
                dpPath[i][j] = dpPath[i - 1][j] + dpPath[i][j - 1];
            }
        }
        console.log(dpPath);
        return dpPath[pointX - 1][pointY - 1];
    }

    function create2DArray(bigNum: number, subNum: number) {
        const pArray: number[][] = [];
        for (let i = 0; i < bigNum; i++) {
            const childArray: number[] = [];
            for (let j = 0; j < subNum; j++) {
                childArray.push(0);
            }
            pArray.push(childArray);
        }
        return pArray;
    }

最小路径和

复杂度分析

时间复杂度:O(mn)其中 m 和 n 分别是网格的行数和列数。需要对整个网格遍历一次,计算 dp 的每个元素的值。 空间复杂度:O(mn),其中 m 和 n 分别是网格的行数和列数。创建一个二维数组 dp,和网格大小相同。

//最小路径和
function minPathSum(grid: number[][]): number {
    //边界情况判断
    if (grid === null || grid.length === 0 || grid[0].length === 0) {
        return 0;
    }

    //根据原始数据深拷贝一个新的数创建一个空的二维数组
    const dp = deepCopyArray(grid);
    //第一个点肯定是
    dp[0][0] = grid[0][0];

    //最左一列没有最小判断
    for (let i = 1;i < grid.length; i++) {
        dp[i][0] = dp[i-1][0] + grid[i][0];
    }
    //最上面一列没有最小判断
    for (let j = 1;j < grid[0].length; j++) {
        dp[0][j] = dp[0][j-1] + grid[0][j];
    }

    for (let i = 1;i < grid.length; i++) {
        for (let j = 1; j < grid[i].length; j++) {
            //当前点位和值来自上一个或者左边一个
            dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j];
        }
    }

    return dp[grid.length - 1][grid[0].length -1];
};

function deepCopyArray(grid:number[][]) {
    const temp: number[][] = [];
    for (let i of grid) {
        const pTemp: number[] = [];
        for (let j of i) {
            pTemp.push(j);
        }
        temp.push(pTemp);
    }
    return temp;
}