DFS&BFS

151 阅读2分钟
  1. 求和为target的组合数

题目:数组中找出和为target的所有组合,每个数可以无限制取

解法:DFS

/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function(candidates, target) {
    const ans = [];
    const dfs = (target, combine, idx) => {
        if (idx === candidates.length) {
            return;
        }
        if (target === 0) {
            ans.push(combine);
            return;
        }
        // 直接跳过
        dfs(target, combine, idx + 1);
        // 选择当前数
        if (target - candidates[idx] >= 0) {
            dfs(target - candidates[idx], [...combine, candidates[idx]], idx);
        }
    }

    dfs(target, [], 0);
    return ans;
};

2.岛的面积

/**
 * @param {number[][]} grid
 * @return {number}
 */
var maxAreaOfIsland = function(grid) {
    let visited = [], stack = [],
        rows = grid.length, cols = grid[0].length,
        max = 0, cur = 0;

    for (let i = 0; i < rows; i++) {
        visited.push(new Array(cols));
    }

    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (!visited[i][j] && grid[i][j] == 1) {
                stack.push([i, j]);
                visited[i][j] = true;
                cur = 1;
                while (stack.length) {
                    let [x, y] = stack.pop();
                    if (x > 0 && grid[x - 1][y] == 1 && !visited[x - 1][y]) {
                        stack.push([x - 1, y]);
                        visited[x-1][y] = true;
                        cur += 1;
                    }
                    if (x < rows - 1 && grid[x + 1][y] == 1 && !visited[x + 1][y]) {
                        stack.push([x + 1, y]);
                        visited[x + 1][y] = true;
                        cur += 1;
                    }
                    if (y > 0 && grid[x][y - 1] == 1 && !visited[x][y - 1]) {
                        stack.push([x, y - 1]);
                        visited[x][y - 1] = true;
                        cur += 1;
                    }
                    if (y < cols - 1 && grid[x][y + 1] == 1 && !visited[x][y + 1]) {
                        stack.push([x, y + 1]);
                        visited[x][y + 1] = true;
                        cur += 1;
                    }
                }
                max = Math.max(max, cur);
            }
        }
    }

    return max;
};
  1. 求二叉树中距离某个结点距离为k的结点集合

题目:二叉树中所有距离目标结点距离为k的结点

tip: 转化为图的广度优先遍历,距离为3即可

解法:设置parent, 广度优先

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {TreeNode} target
 * @param {number} k
 * @return {number[]}
 */
//设置指向parent的指针,相当于无向图的广度优先遍历
var distanceK = function(root, target, k) {
    let parent = {}, visited = {}, queue = [];
    
    const setParent = (node, par) => {
        if (!node) return;
        parent[node.val] = par;
        setParent(node.left, node);
        setParent(node.right, node);
    };

    queue.push({
        node: target,
        level: 0
    });
    visited[target.val] = true;
    
    setParent(root, null);

    while (queue.length) {
        let item = queue.shift(),
            node = item.node,
            level = item.level;

        if (level == k) {
            return [node.val].concat(queue.map(item => item.node.val));
        } else {
            if (node.left && !visited[node.left.val]) {
                visited[node.left.val] = true;
                queue.push({
                    node: node.left,
                    level: level + 1
                });
            }
            if (node.right && !visited[node.right.val]) {
                visited[node.right.val] = true;
                queue.push({
                    node: node.right,
                    level: level + 1
                });
            }
           
            if (parent[node.val] && !visited[parent[node.val].val]) {
                visited[parent[node.val].val] = true;
                queue.push({
                    node: parent[node.val],
                    level: level + 1
                });
            }
            
        }
    }
    
    return [];
};
  1. 腐烂的橘子

BFS

/**
 * @param {number[][]} grid
 * @return {number}
 */
//广度优先遍历一张图
var orangesRotting = function(grid) {
    let queue = [], max = 0, visited = [];
    for (let i = 0; i < grid.length; i++) {
        visited.push(new Array(grid[0].length));
    }

    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid[0].length; j++) {
            if (grid[i][j] == 2) {
                visited[i][j] = true;
                queue.push({
                    orange: [i, j],
                    level: 0
                });
            }
        }
    }

    while (queue.length) {
        let item = queue.shift(),
            [x, y] = item.orange,
            level = item.level;

        max = Math.max(max, level);

        if (x > 0 && grid[x-1][y] == 1 && !visited[x-1][y]) {
            visited[x - 1][y] = true;
            queue.push({
                orange: [x-1, y],
                level: level + 1
            });
        }
        if (y < grid[0].length - 1 && grid[x][y + 1] == 1 && !visited[x][y+1]) {
            visited[x][y+1] = true;
            queue.push({
                orange: [x, y + 1],
                level: level + 1
            });
        }
        if (x < grid.length - 1 && grid[x+1][y] == 1 && !visited[x+1][y]) {
            visited[x+1][y] = true;
            queue.push({
                orange: [x + 1, y],
                level: level + 1
            });
        }
        if (y > 0 && grid[x][y-1] == 1 && !visited[x][y-1]) {
            visited[x][y-1] = true;
            queue.push({
                orange: [x, y - 1],
                level: level + 1
            });
        }
    }

    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid[0].length; j++) {
            if (grid[i][j] != 0 && !visited[i][j]) {
                return -1;
            }
        }
    }

    return max;
};

排序:

  1. 找出出现频率最高的k个单次

题目要求:O(n)复杂度, 每个数大小[1, n]

tip: 先遍历统计次数,然后设置一个桶数组。桶数组的下标代表出现次数。借助数组下标天然可排序的优势。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var topKFrequent = function(nums, k) {
    let map = {}, bucket = new Array(nums.length + 1), max = 0;
    nums.forEach(num => {
        map[num] = map[num] ? (map[num] + 1) : 1;
    });

    for (let num in map) {
        if (map.hasOwnProperty(num)) {
            if(!bucket[map[num]]) {
                bucket[map[num]] = [];
            } 
            bucket[map[num]].push(num);
            max = Math.max(map[num], max);
        }
    }

    

    let ans = [];
    for (let i = max; i >= 1; i--) {
        if (!bucket[i]) continue;
        if (ans.length + bucket[i].length <= k) {
            ans = ans.concat(bucket[i]);
        } else {
            let subarr = bucket[i].slice(ans.length + bucket[i].length - k);
            ans = ans.concat(subarr);
            return ans;
        }
    }

    return ans;
};

最长系列:

  1. 最长连续子序列

题目:arr中找出最长的,数值上连续的子序列的长度。 比如[100, 1, 200, 4, 3, 2], 最长子序列为[1, 2, 3, 4]。

tip: 要求O(n)的题目可以考虑借助空间。本题借助map. 边遍历边迭代是一个求解一维数组相关题目的思路。

解法:设置map记录每一个数是否出现过。当遍历到nums[i]时,如果不存在nums[i-1](证明它是当前子序列第一个数),那么不断判断map[nums[i+1...n]]是否存在,更新max.

var longestConsecutive = function(nums) {
   let map = {}, ans = 0;
   nums.forEach(num => {
       map[num] = true;
   });

   for (i = 0; i < nums.length; i++) {
       if (!map[nums[i] - 1]) {
           let count = 1;
           while (map[nums[i] + count]) {
               count++;
           }
           ans = Math.max(count, ans);
       }
   }

   return ans;
};

链表:

  1. 删除第n个结点

解法:两个指针,一个比另一个向前n步

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
   let a = head, b = head, i = 0;
   while (i < n) {
       a = a.next;
       i++;
   }

   if (!a) return head.next;

   while(a.next) {
       a = a.next;
       b = b.next;
   }

   b.next = b.next.next;

   return head;
};

OO:

  1. LRU 缓存 题目描述: 固定大小c的cache, 实现get和put O(1)时间内实现,如果cache已达最大值,将最久没有被使用的cache移除。

tip: 使用map, 使用双向链表实现优先队列。双向链表要同时记录head和tail. 双向链表的特点是,可以很容易得到一个结点的前驱。 真正的缓存放在双向链表中,map为key到缓存结点的映射。

解法: get: 查找map直接得到有还是没有。如果有,需要移动该节点到head. 时间O(1)——双向链表移动节点是O(1) put: 如果还有空间,直接插入到head前。如果没有空间了, 修改tail部分的结点为新值,并移动到头部(或者直接删除,然后插入一个),也是O(1).

/**
 * @param {number} capacity
 */
var LRUCache = function(capacity) {
   this.capacity = capacity;
   this.size = 0;
   this.map = {};
   this.head = null;
   this.tail = null;
};

/** 
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function(key) {
   let node = this.map[key];
   if (node) {
       if (node.left) {
           node.left.right = node.right;
           if (node.right) {
                node.right.left = node.left;
            } else {
                this.tail = node.left;
                this.tail.right = null;
            }
            node.left = null;
            node.right = this.head;
            this.head.left = node;
            this.head = node;
       } 
       return node.val;
   } else {
       return -1;
   }
};

/** 
 * @param {number} key 
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function(key, value) {
    if (!this.capacity) return;
    let node = this.map[key];
    //已有该值
    if (node) {
        node.val = value;
        //将该结点移动到head
        //如果本是第一个结点,直接返回
        if (node == this.head) {
            return;
        } else {
            //左边结点left指向右边结点
            node.left.right = node.right;
            //如果有右边结点,指向左边结点
            if (node.right) {
                node.right.left = node.left;
            } else {
                //否则node是尾部结点,调整this.tail;
                this.tail = node.left;
            }
            this.head.left = node;
            node.right = this.head;
            node.left = null;
            this.head = node;
        }
    //不存在node;
    } else {
        if (this.size < this.capacity) {
            this.size++;
            node = {
                key: key,
                val: value,
                left: null,
                right: this.head
            };
            if (!this.head) {
                this.head = this.tail = node;
            } else {
                this.head.left = node;
                this.head = node;
            }
        } else {
            delete this.map[this.tail.key];
            node = this.tail;
            node.key = key;
            node.val = value;
            if (node != this.head) {
                this.tail = node.left;
                this.tail.right = null;
                node.left = null;
                node.right = this.head;
                this.head.left = node;
                this.head = node;
            }
        }
        this.map[key] = node;
    }
};

  1. TicTacToe

题目:三子棋

解法:记录每个选手每一行 每一列 每一对角线的和

/**
 * Initialize your data structure here.
 * @param {number} n
 */
var TicTacToe = function(n) {
    this.n = n;
    this.rowSum = [new Array(n), new Array(n)];
    this.colSum = [new Array(n), new Array(n)];
    this.diaSum1 = new Array(2);
    this.diaSum2 = new Array(2);
};

/**
 * Player {player} makes a move at ({row}, {col}).
        @param row The row of the board.
        @param col The column of the board.
        @param player The player, can be either 1 or 2.
        @return The current winning condition, can be either:
                0: No one wins.
                1: Player 1 wins.
                2: Player 2 wins. 
 * @param {number} row 
 * @param {number} col 
 * @param {number} player
 * @return {number}
 */
TicTacToe.prototype.move = function(row, col, player) {
    this.rowSum[player - 1][row] = (this.rowSum[player - 1][row] || 0) + 1;
    if(this.rowSum[player - 1][row] == this.n) return player;

    this.colSum[player - 1][col] = (this.colSum[player - 1][col] || 0 ) + 1; 
    if (this.colSum[player - 1][col] == this.n) return player;

    if (row == col) {
        this.diaSum1[player - 1] = (this.diaSum1[player - 1] || 0) + 1;
        if (this.diaSum1[player-1] == this.n) return player;
    }

    if (row + col == this.n - 1) {
       this.diaSum2[player - 1] = (this.diaSum2[player - 1] || 0) + 1;
        if (this.diaSum2[player-1] == this.n) return player; 
    }

    return 0;
};

/** 
 * Your TicTacToe object will be instantiated and called as such:
 * var obj = new TicTacToe(n)
 * var param_1 = obj.move(row,col,player)
 */

双指针:

  1. 找出字符串中无重复字符的最长子串

tip: 双指针都从0开始。 循环不变量是双指针l和r之间永远没有重复字符。 通过map记录字符有没有出现过。

解法:一旦字符s[r]出现过,l移动到字符s[r]出现的位置l2并加1, 顺便抹去l到l2之间map的记录。同时如果此时r - l比上次大,更新最大值。

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
   if (!s) return 0;
   if (s.length == 1) return 1;

   //循环不变量是l和r之间永远没有重复的字符。
   var l = 0, r = 0, max = 0, map = {};
   while(r < s.length) {
     if (typeof map[s[r]] != 'undefined') {
        if (r - l > max) {
          max = r - l;
         }
         while(l <= map[s[r]]) {
             delete map[s[l]];
             l++;
         }
     }
     map[s[r]] = r;
     r++;
   }
   if (r - l > max) {
       max = r - l;
   }

   return max;
};
  1. 3sum

tip: 将数组排序,固定一个数,另外两个数双指针。 冒泡排序partition循环不变量: start + 1到i - 1永远 <= target。j + 1到end永远大于target

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

const swap = (nums, i, j) => {
    let tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

const partition = (nums, start, end) => {
   
   let target = nums[start], i = start, j = end;
   while (i < j) {
       if (nums[i + 1] > target) {
           swap(nums, i + 1, j);
           j--;
       } else {
           i++;
       }
   }
   return i;
}

const quicksort = (nums, start, end) => {
    if (start >= end) return;

    let pos = partition(nums, start, end);
    swap(nums, start, pos);
    quicksort(nums, start, pos - 1);
    quicksort(nums, pos + 1, end);
}

var threeSum = function(nums) {
   let result = [];
   if (nums.length < 3) return [];

   quicksort(nums, 0, nums.length - 1);

   for (let i = 0; i < nums.length - 2; i++) {
       let m = i + 1, k = nums.length - 1;
       while(m < k) {
           let sum = nums[i] + nums[m] + nums[k];
           if (sum == 0) {
               result.push([nums[i], nums[m], nums[k]]);
               m++;
               k--;
               while(m < k && nums[m] == nums[m-1])m++;
               while(m < k && nums[k] == nums[k+1])k--;
           } else if (sum > 0) {
               k--;
           } else {
               m++;
           }
       }
       while (nums[i + 1] == nums[i]) i++;
   }

   return result;
};
  1. 0 1 2排序
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */

var swap = function(nums, i, j) {
    let tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

var sortColors = function(nums) {
    if (nums.length == 1) return;
    if (nums.length == 2) {
        if (nums[0] > nums[1]) {
            swap(nums, 0, 1);
        }
        return;
    }

    let left = 0, right = nums.length - 1;
    while (nums[left] == 0) left++;
    while (nums[right] == 2) right--;
    
    let cur = left;
    while (cur <= right) {
        if (nums[cur] == 0) {
            if (cur >= left) {
                swap(nums, cur, left);
                while(nums[left] == 0) left++;
            } else {
                cur++;
            }
        } else if (nums[cur] == 2) {
            swap(nums, cur, right);
            right--;
        } else {
            cur++;
        }
    }
};
  1. 最大水容积

题目:给定挡板高度数组heights, 求最大能容纳的水的容积。

解法:从最左和最右开始(宽最大),计算容积。缩进高度比较小的边(毕竟这样才有改善余地)

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
   let i = 0, j = height.length - 1, max = 0;
   while (i < j) {
       let v = (j - i) * Math.min(height[i], height[j]);
       max = Math.max(v, max);
       if (height[i] < height[j]) {
           i++;
       } else {
           j--;
       }
   }

   return max;
};

字母计数:

  1. 将字符串按照「字谜」分组

解法:字母计数数组 + 哈希表。(计数数组转化为字符串再作为哈希表的key)

var groupAnagrams = function(strs) {
    let map = {};

    strs.forEach(str => {
        let arr = new Array(26).fill(0);
        for (let i = 0; i < str.length; i++) {
            arr[str[i].charCodeAt(0) - 97]++;
        }
        let key = arr.map(num => num + '').join('-');
        map[key] = map[key] || [];
        map[key].push(str);
    });

    return Object.values(map);
};