算法训练营

Posted haishen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法训练营相关的知识,希望对你有一定的参考价值。

概念:算法与数据结构相辅相成

  算法是为了解决某一个具体的问题,提出来的一个解法

  数据结构是为了支撑这次解法,所提出的一种存储结构

 

1、两数之和(LeetCode1)

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

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

题解:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    let obj = {}
    for(let i=0; i<nums.length; i++){
        let temp = nums[i]
        if(!obj.hasOwnProperty(temp)){ // 这里也可以用 temp in obj 来判断
            obj[target - temp] = i
        }else{
            return [obj[temp], i]
        }
    }
};

 

2、三数之和(LeetCode15)

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

 

思路:

  • 首先对数组进行排序,排序后固定一个数 nums[i]nums[i],再使用左右指针指向 nums[i]nums[i]后面的两端,数字分别为 nums[L]nums[L] 和 nums[R]nums[R],计算三个数的和 sumsum 判断是否满足为 00,满足则添加进结果集
  • 如果 nums[i] 大于 0,则三数之和必然无法等于 0,结束循环
  • 如果 nums[i] == nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过
  • 当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++
  • 当 sum == 0 时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−
  • 时间复杂度:O(n^2)
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    let ans = [];
    const len = nums.length;
    if(nums == null || len < 3) return ans;
    nums.sort((a, b) => a - b); // 排序
    for (let i = 0; i < len ; i++) {
        if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
        if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
        let L = i+1;
        let R = len-1;
        while(L < R){
            const sum = nums[i] + nums[L] + nums[R];
            if(sum == 0){
                ans.push([nums[i],nums[L],nums[R]]);
                while (L<R && nums[L] == nums[L+1]) L++; // 去重
                while (L<R && nums[R] == nums[R-1]) R--; // 去重
                L++;
                R--;
            }
            else if (sum < 0) L++;
            else if (sum > 0) R--;
        }
    }        
    return ans;
};

 

3、斐波那契数(LeetCode509)

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

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

给定 N,计算 F(N)

/**
 * @param {number} N
 * @return {number}
 */
var fib = function(N) {
    if(N === 0 || N === 1){
        return N
    }
    let cache = []
    for(let i=0; i<=N; i++){
        if( i == 0 || i == 1 ){
            cache[i] = i
        }else{
            cache[i] = cache[i-1] + cache[i-2]
        }
    }
    return cache[N]
};

 

 

4、有效的括号(LeetCode20)

给定一个只包括 ‘(‘‘)‘‘{‘‘}‘‘[‘‘]‘ 的字符串,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    let left = [‘(‘, ‘[‘, ‘{‘]
    let right = [‘)‘, ‘]‘, ‘}‘]
    let stack = []
    let arr = s.split(‘‘)
    for(let i=0; i<arr.length; i++){
        let temp = arr[i]
        if(left.indexOf(temp) !== -1){
            stack.push(temp)
        }else{
            if(left.indexOf(stack.pop()) === right.indexOf(temp)){
                continue
            }else{
                return false
            }
        }
    }
    if(stack.length === 0){
        return true
    }else{
        return false
    }
};

 

5、反转链表(LeetCode206)

反转一个单链表。

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

代码如下

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    if(!head) return head
    let ele = {
        next: head
    }
    let cur = head
        prev = null
    while(cur){
        let temp = cur.next
        cur.next = prev
        prev = cur
        cur = temp
        
        ele.next = prev
    }
    return ele.next
};

 

6、环形链表(LeetCode141)

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

解法1:数组判重

环中两个节点相遇,说明在不同的时空内会存在相遇的那一刻,过去与未来相遇,自己和前世回眸,即是重复

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    let res = [];
    while(head != null){
        if(res.includes(head)){
            return true;
        }else{
            res.push(head);
        }
        head = head.next;
    }
    return false;
};

 

解法2:标记法

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    while(head && head.next){
        if(head.flag){
            return true;
        }else{
            head.flag = 1;
            head = head.next;
        }
    }
    return false;
};

 

解法3:双指针

  • 在一个圆里,运动快的点在跑了n圈后,一定能相遇运动慢的点。
    • 对应链表,就是两个指针重合
  • 如果不是圆,哪怕是非闭合的圆弧,快点一定先到达终点??,则说明不存在环
    • 对应链表,就是结尾指针指向null
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    if(!head || !head.next) return false;
    let fast = head.next;
    let slow = head;
    while(fast != slow){
        if(!fast || !fast.next){
            return false;
        }
        fast = fast.next.next;
        slow = slow.next;
    }
    return true;
};

 

7、环形链表2(LeetCode142)

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    // 链表中有环,slow和start重新出发,每次向前走一步
    // 假设head到入环节点的距离为 a ,发生套圈时,慢节点走了 a + b,快节点走了 a + 2b + c
    //     则说明 2(a+b) = a+2b+c
    //     由此可得:a = c 
    // 然后在此处,start节点从头出发,慢节点从被套圈的地方出发,这两个节点每次都各走一步
    // 最后相遇点即是入环点
    let fast = head,
        slow = head,
        start = head
    while(fast && fast.next){
        fast = fast.next.next;
        slow = slow.next;
        if(fast == slow){
            while(slow && start){
                if(slow == start) return slow
                slow = slow.next
                start = start.next
            }
        }
    }
    return null
};

 

8、相同的树(LeetCode100)

给定两个二叉树,编写一个函数来检验它们是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {boolean}
 */
var isSameTree = function(p, q) {
    // 如果p和q都是null,空二叉树,那么他们相等
    if(p===null && q===null) return true
    if(p===null || q===null) return false
    if(p.val !== q.val) return false

    // p和q的值相等,需要递归判定左右子树
    return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
};

 

9、翻转二叉树(LeetCode226)

翻转一棵二叉树。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    // 终止条件
    if(root === null){
        return root
    }
    [root.left, root.right] = [invertTree(root.right), invertTree(root.left)]
    return root
};

 

10、二叉树的前序遍历(LeetCode144)

给定一个二叉树,返回它的 前序 遍历。

方法1:递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function(root, arr=[]) {
    if(root){
        // 前序遍历:先处理自己,再处理左右
        arr.push(root.val)
        preorderTraversal(root.left, arr)
        preorderTraversal(root.right, arr)
    }
    return arr
};

 

方法2:迭代

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function(root) {
    // 开始遍历,有一个stack存储
    // left入栈,直到left为空
    // 节点出站,右孩子为目标节点
    let stack = [],
        cur = root,
        result = []
    while(cur || stack.length>0){
        while(cur){
            stack.push(cur) // 后面要通过cur找他的right
            result.push(cur.val)
            cur = cur.left
        }
        cur = stack.pop()
        cur = cur.right
    }
    return result
};

 

11、二叉树的中序遍历(LeetCode94)

给定一个二叉树,返回它的中序 遍历。

方法1:递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function(root, arr=[]) {
    if(root){
        inorderTraversal(root.left, arr)
        arr.push(root.val)
        inorderTraversal(root.right, arr)
    }
    return arr
};

 

方法2:迭代

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function(root) {
    let stack = [],
        arr = [],
        cur = root
    // cur为null时,如果stack内还有值,说明还需要继续遍历右子树
    while(cur || stack.length>0){
        if(cur){
            stack.push(cur)
            cur = cur.left
        }else{
            cur = stack.pop()
            arr.push(cur.val)
            cur = cur.right
        }
    }
    return arr
};

 

12、二叉树的后序遍历(LeetCode145)

给定一个二叉树,返回它的 后序 遍历。

方法1:递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function(root, arr=[]) {
    if(root){
        postorderTraversal(root.left, arr)
        postorderTraversal(root.right, arr)
        arr.push(root.val)
    }
    return arr
};

 

方法2:迭代

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function(root) {
    let stack = [],
        arr = [],
        cur = root
    // 倒序解
    // 每次先入左节点, 然后入右节点
    // 每次把值都插到数组的最前面
    while(cur || stack.length>0){
        arr.unshift(cur.val)
        if(cur.left) stack.push(cur.left)
        if(cur.right) stack.push(cur.right)
        cur = stack.pop()
    }
    return arr
};

 

13、验证二叉搜索树(LeetCode98)

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

如果我们使用中序遍历,得到的结果一定是递增的

方法1:迭代

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isValidBST = function(root) {
    let stack = [],
        arr = [],
        cur = root,
        flag = true
    while(cur || stack.length>0){
        if(cur){
            stack.push(cur)
            cur = cur.left
        }else{
            cur = stack.pop()
            arr.push(cur.val)
            cur = cur.right
        }
    }
    if(arr.length < 2) return flag

    arr.reduce((pre, cur)=>{
        if(pre >= cur){
            flag = false
        }
        return cur
    })

    return flag
};

 

方法2:递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isValidBST = function(root, min=null, max=null) {
    if(!root) return true

    if(min && root.val<=min.val){
        return false
    } 
    if(max && root.val>=max.val){
        return false
    }
    
    return isValidBST(root.left, min, root) && isValidBST(root.right, root, max)
};

 

14、二叉树的最大深度(LeetCode104)

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

叶子节点是指没有子节点的节点。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if(!root) return 0
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};

 

15、二叉搜索树的最近公共祖先(LeetCode235)

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
// 由于是二叉搜索树,
//     如果两个p和q的值都大于root的值,则递归root.right
//     如果两个p和q的值都小于root的值,则递归root.left
//     否则 root 就是最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
    let root_val = root.val,
        p_val = p.val,
        q_val = q.val
    if(p_val>root_val && q_val>root_val){
        return lowestCommonAncestor(root.right, p, q)
    }else if(p_val<root_val && q_val<root_val){
        return lowestCommonAncestor(root.left, p, q)
    }else{
        return root
    }
};

 

16、二叉树的最近公共祖先(LeetCode236)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

 

递归

  • 临界条件:最
    • 根节点是空节点
    • 根节点是q节点
    • 根节点是p节点
  • 根据临界条件,此题相当于查找以root为根节点的树上是否有 q 节点 或 p 节点
    • 有则返回 q 或 p 节点
    • 无则返回 null
    左右子树分别进行递归,即查找左右子树上是否有 q 节点 或 p 节点
    • 左右子树均无 q 节点 或 p 节点
    • 左子树能找到,右子树不能找到,则返回左子树的查找结果
    • 右子树能找到,左子树不能找到,则返回右子树的查找结果
    • 左右子树均能找到,则说明此时 q 和 p 节点在当前root的左右两侧,返回root
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    if(root == null || root == p || root == q){
        return root;
    }
    let left = lowestCommonAncestor(root.left,p,q);
    let right = lowestCommonAncestor(root.right,p,q);
    if(left != null && right != null){
        return root;
    }
    return left ? left : right;
};

 

17、Pow(x, n)(LeetCode50)

实现 pow(xn) ,即计算 x 的 n 次幂函数。

示例1:

输入: 2.00000, 10
输出: 1024.00000

示例2:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

代码:

/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function(x, n) {
    if(n === 0) return 1
    if(n<0) return 1 / myPow(x, -n)
    // 二分+递归的思想,n每次向右移动一位,如果该位上有值(二进制),则结果 * x
    // 每次让 x = x*x
    if(n%2 === 1){
        return myPow(x*x, Math.floor(n/2)) * x
    }else{
        return myPow(x*x, Math.floor(n/2))
    }
};

 

18、全排列(LeetCode46)

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:

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

代码:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function(nums) {
    let list = []
    backtrack(list, [], nums)
    return list
};

function backtrack(list, temp, nums){
    // 终止条件
    if(temp.length === nums.length){
        return list.push([...temp])
    }
    for(let i=0; i<nums.length; i++){
        if(temp.includes(nums[i])) continue
        temp.push(nums[i])
        backtrack(list, temp, nums)
        temp.pop()
    }
}

 

19、x的平方根(LeetCode69)

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例1:

输入: 4
输出: 2

示例2:

输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 
     由于返回类型是整数,小数部分将被舍去。

代码:

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    if(x===0 || x===1) return x
    let left = 1,
        right = x
    while(left<=right){
        let middle = parseInt((left + right)/2)
        if(middle * middle === x){
            return middle
        }else if(middle * middle > x){
            right = middle - 1
        }else{
            left = middle + 1
        }
    }
    return right
};

 

20、单词搜索(LeetCode79)

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:

board =
[
  [‘A‘,‘B‘,‘C‘,‘E‘],
  [‘S‘,‘F‘,‘C‘,‘S‘],
  [‘A‘,‘D‘,‘E‘,‘E‘]
]

给定 word = "ABCCED", 返回 true.
给定 word = "SEE", 返回 true.
给定 word = "ABCB", 返回 false.
/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
var exist = function(board, word) {
    // 1、怎么找
    // 2、什么时候终止
    // 3、find内部,怎么找下一步(缓存存储中间的过程)

    if(board.length === 0) return false
    if(word.length === 0) return true
    const row = board.length
    const col = board[0].length
    // 怎么找
    for(let i=0; i<row; i++){
        for(let j=0; j<col; j++){
            const ret = find(i,j,0)
            if(ret) return true
        }
    }
    return false

    // i,j是当前在矩阵中的位置,cur是单词word的第几个
    function find(i, j, cur){
        // 越界要停止
        if(i>=row || i<0) return false
        if(j>=col || j<0) return false
        const letter = board[i][j]
        // 字母不匹配
        if(letter !== word[cur]) return false
        // 找到最后一个了,而且相等
        if(cur === word.length-1) return true
        // ---终止条件
        board[i][j] = null // 当前路径标记为null,表示不可使用了
        const ret = find(i+1,j,cur+1) ||
                    find(i-1,j,cur+1) ||
                    find(i,j+1,cur+1) ||
                    find(i,j-1,cur+1)
        board[i][j] = letter

        return ret
    }
};

 

21、n皇后(LeetCode51)

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

技术图片

 

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

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

示例:

输入: 4
输出: [
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function(n) {
    // 索引特点:在0,1时 右边的斜线位置  1,2  2,3  :发现 行-列一样
    // 在0,2时  左边的斜线位置 1,1  2,0  :发现 :行+列一样
    // 1、行不能一样 按照行查找
    // 2、列不能一样
    // 3、行-列不能一样
    // 4、行+列不能一样

    // tmp为了记录之前的路径
    // tmp的索引是行数据,值是列数据,摆放的棋子
    // 例如:[2,4,1]代表 第一个摆放在2这个位置,第二列摆放在4,第三列摆放在1
    let ret = []
    // 查找第0行
    find(0)
    return ret
    function find(row, tmp=[]){
        if(row===n){
            // 找完了 n-1就已经是最后一行了 tmp就是所有的摆放位置
            ret.push(tmp.map(c=>{
                let arr = new Array(n).fill(‘.‘)
                arr[c] = ‘Q‘
                return arr.join(‘‘)
            }))
        }
        // 1、如何查找
        for(let col=0; col<n; col++){
            // 是不是不能放
            let cantSet = tmp.some((ci,ri)=>{
                // ci和ri是之前摆放棋子的行列索引
                // col和row是当前所在位置的索引
                return ci===col || 
                       (ri-ci)===(row-col) ||
                       (ri+ci)===(row+col)
            })
            if(cantSet) continue
            // 如果能放,直接下一行
            find(row+1, [...tmp, col])
        }
    }
};

 

 22、删除排序数组中的重复项(LeetCode26)

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

 示例1:

给定数组 nums = [1,1,2], 
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 
你不需要考虑数组中超出新长度后面的元素。

示例2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。

 

由以上分析可知 符合题意的解题关键在于 确定最近一次重复元素第一次出现的位置 && 替换此位置上的元素为遍历的当前元素

  • 不通过计算重复个数 通过追踪重复元素的指针位置 并动态更新 => 
  • 增加一个 是重复元素且是第一次出现的位置指针 r 默认初始化为 0 ,数组遍历从 i = 1 开始
    • 当且仅当遇到下一个不相同即不重复的元素时,更新指针位置为下一个元素(虽然是重复元素但是还是要保留第一个不能被替换) && nums[r] = nums[i]
    • 否则指针位置不动,原数组继续遍历
    • 数组遍历完后 返回 r+1 (为什么加1?因为是索引位置,而题目要求返回的是长度)

 代码:

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

 

 23、最大数(LeetCode179)

 给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数。

示例1:

输入: [10,2]
输出: 210

示例2:

输入: [3,30,34,5,9]
输出: 9534330

说明: 输出结果可能非常大,所以你需要返回一个字符串而不是整数。

代码:

/**
 * @param {number[]} nums
 * @return {string}
 */
var largestNumber = function(nums) {
    let arr =  nums.sort((a,b)=>{
        return `${b}${a}` - `${a}${b}`
    })
    return arr[0] ? arr.join(‘‘) : ‘0‘
};

 

 24、盛水最多的容器(LeetCode11)

给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

技术图片

 

 示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49

代码:

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let L = 0, // 左边界
        R = height.length - 1, // 右边界
        res = 0, // 最大值
        lastHeight = 0; // 存放上一次的高度
    while(L<R){
        if(height[L]<height[R]){ // 以左边界为高
            // 只考虑移动后高度增加的情况(移动后宽度肯定变小,若高度也变小,则面积是一定小于之前的)
            if(height[L]>lastHeight){
                res = Math.max(res, (R-L)*height[L]) // 将最大值赋值给res
                lastHeight = height[L] // 将高度赋值给lastHeight
            }
            L++
        }else{ // 以右边界为高
            if(height[R]>lastHeight){
                res = Math.max(res, (R-L)*height[R])
                lastHeight = height[R]
            }
            R--
        }
    }
    return res
};

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1

删除排序数组中的重复项

以上是关于算法训练营的主要内容,如果未能解决你的问题,请参考以下文章

片段(Java) | 机试题+算法思路+考点+代码解析 2023

使用多个特征的支持向量机 (SVM) 训练

代码随想录算法训练营第四天 | 24.两两交换链表

代码随想录算法训练营第30天

代码随想录算法训练营第七天 | 454.四数相加II 383.赎金信 

代码随想录算法训练营第一天 | 704. 二分查找27. 移除元素