算法学习随记 - 数组、双指针、哈希表、链表、二叉树、排序、回溯、深度、广度优先遍历相关的几道解题思路

一 、有关数组的几道题

leetcode 238 除自身以外的乘积

原题

/**
* 思路:每个元素的结果等于左边的元素乘以右边的元素
 * @param nums
 * @return
 */
public int[] productExceptSelf(int[] nums) {
    int k = 1;
    int len = nums.length;
    int[] res = new int[len];
    //计算所有元素左边的乘积
    for(int i=0;i<len;i++){
        res[i] = k;
        k = k*nums[i];
    }
    //计算右边的乘积
    k = 1;
    for(int j=len-1;j>=0;j--){
        res[j] *= k;
        k *= nums[j];
    }
    return res;
}

leetcode 55 跳跃游戏

原题

/**
 * 思路:遍历所有节点,每个节点都有最远可达的地方,如果下一个节点在当前最远可到达范围内,那么看当前节点最远可达是否更远,如果遍历完发现最远可达
 * 超过了最后一个位置,那么返回true!
 * @param nums
 * @return
 */
public boolean canJump(int[] nums) {
    if(nums==null) {
        return false;
    }
    int mostJump = 0;
    int len = nums.length;
    for (int i=0;i<len;i++){
        if (i<=mostJump){//当前i可达
            mostJump = Math.max(mostJump,i+nums[i]);//更新最远可达
            if(mostJump>=len-1) {
                return true;
            }
        }
    }
    return false;
}

leetcode560 和为K的连续子数组

原题

/**
 * 普通区间方法,使用了 前缀求和 的方式减少每次都要计算区间和
 * (1)从数组第一位开始计算每一位上的【前缀和】--> preSum即当前下标的前缀和
 * (2)求在某个区间[i,j]之间子数组的和时,使用preSum[j+1]-preSum[i]
 */
public int subarraySum2(int[] nums, int k) {
    if(nums==null) {
        return 0;
    }
    int count = 0;
    int len = nums.length;
    int[] preSum = new int[len+1];
    //计算前缀和,便于后面计算各区间和
    preSum[0] = 0;
    //相对于0开始
    for(int i=0;i<len;i++){
        preSum[i+1] = preSum[i] + nums[i];
    }
    //O(n^2)查找所有结果
    for(int left=0;left<=len;left++){
        for(int right=left;right<len;right++){
            if(preSum[right+1]-preSum[left] == k) {
                count ++;
            }
        }
    }
    return count;
}

leetcode31 下一个排列

原题

/**
 * 思路:
 * (1)从右往左找到第一个a[i-1]<a[i] 的数
 * (2)再从a[i-1]的右侧找到一个大于它的最小的数,交换位置
 * (3)之后在将a[i-1]右边的所有数倒置
 *
 */
public void nextPermutation(int[] nums) {
    if (nums==null) {
        return;
    }
    int len = nums.length;
    int i = len-1;
    //从右往左找到第一个不按逆序排序的数!就是可以进行交换的
    while(i>0 && nums[i]<=nums[i-1]){
        i--;
    }
    //若否则证明整个数组是有逆序的了不存在下一个值,直接反转整个数组
    if(i!=0 ){
        int j = len-1;
        while( nums[i-1]>=nums[j] ){//找到右边第一个比它大的最小的值进行交换
            j--;
        }
        int temp = nums[j];
        nums[j] = nums[i-1];
        nums[i-1] = temp;
    }
    reverse(nums,i);
}

二、双指针的几道题

leetcode75 颜色分类

public static void sortColors(int[] nums) {
    int len ;
    if (nums==null || (len = nums.length)==0) {
        return;
    }
    //指向0 的最右边界
    int p0 = 0;
    //指向2 的最左边界
    int p2 = len-1;
    for (int curr=0;curr<len;curr++){
        if (nums[curr]==1) {
            continue;
        }
        else if (nums[curr]==0) {
            int temp = nums[curr];
            nums[curr] = nums[p0];
            nums[p0] = temp;
            p0++;
        }else {
            int temp = nums[curr];
            nums[curr] = nums[p2];
            nums[p2] = temp;
            p2--;
        }
    }
}

leetcode 11 乘最多水的容器

原题

/**
 * 定义两个指针left和right
 * 水量小的指针往内移动可能变大,大的一边往内很大可能变小,因此每次都移动小的一边,计算出每次水量的大小
 */
public int maxArea(int[] height) {
    int len ;
    if ( height==null || (len = height.length)<2 ) {
        return 0;
    }
    int max = 0;
    int right = len  - 1;
    int left = 0;
    while(right>left){
        max = Math.max(max,( right - left )*Math.min(height[left],height[right]));
        if(height[right]>height[left]) {
            left++;
        }
        else {
            right--;
        }
    }
    return max;
}

leetcode15 三数之和

/**
 * 双指针的方法:
 * (1)排序用以去重,去重的关键在于第一次枚举i和第二次枚举j都不要和上一次的枚举重复;
 * (2)O(n^2)的枚举 + 双指针 (这里的双指针体现在当第二层枚举不断增大时,第三层的指针的范围越来越小)
 * @param nums
 * @return
 */
public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    int len ;
    if ( (len=nums.length) <3 ) {
        return res;
    }
    //排序是为了去重,默认升序
    Arrays.sort(nums);
    //循环查找所有三元组
    //循环a
    for (int i=0;i<len;i++){
        //保证一层不重复
        if(i>0 && nums[i]==nums[i-1]) {
            continue;
        }
        //循环b
        for (int j=i+1;j<len;j++){
            //二层不重复
            if (j>i+1 && nums[j]==nums[j-1]) {
                continue;
            }
            int target = nums[i] + nums[j];
            int third = len-1;
            //使用third作为指针从最右边往左移动,直到找到相加为0 的值
            while(third>j && target+nums[third]>0){
                --third;
            }
            if (third==j) {
                break;
            }
            if (target+nums[third]==0) {
                res.add(Arrays.asList(nums[i],nums[j],nums[third]));
            }
        }
    }
    return res;
}

leetcode42 接雨水(困难)

/**
 * 思路:
 * 定义双指针left和right,并记录左右两边最高柱子left_max,right_max
 * 对于位置left而言,它左边最大值一定是left_max,右边最大值“大于等于”right_max;
 * 某一时刻,如果left_max<right_max成立,那么左边肯定能存水。无论右边将来
 * 会不会出现更大的right_max,都不影响这个结果。
 * 所以当left_max<right_max时,我们就希望去处理left下标,反之,我们希望去处理right下标。
 */
public int trap(int[] height) {
    int len;
    if(height==null || (len = height.length)<3) {
        return 0;
    };
    //左边柱子最高值
    int left_max = 0;
    //右边柱子最高值
    int right_max = len -1;
    int left = 0;
    int right = len - 1;
    int rainwater = 0;
    while(left<right){
        //左边柱子低,那么左边left是一定可以存到水的,且可以根据left_max计算
        if (height[left]<height[right]){
            //若左边出现了更高的柱子,那么是存不到水的
            if (height[left]>height[left_max]) {
                left_max = left;
            }
            //存水
            else if(height[left]<height[left_max]) {
                rainwater +=  height[left_max] - height[left];
            }
            left++;
        }else{//右边水位低,那么右边right是一定可以存到水的,且可以根据right_max计算
            if (height[right]>height[right_max]) {
                right_max = right;
            }
            else if(height[right]<height[right_max]) {
                rainwater +=  height[right_max] - height[right];
            }
            right--;
        }
    }
    return rainwater;
}

三、哈希表的几道题

leetcode49 字母异位词

//将每个词都进行排序之后亏可以使用哈希表判断是否是字母异位词
public List<List<String>> groupAnagrams(String[] strs) {
   HashMap<String,List<String>> map = new HashMap<>();
    for(int i=0;i<strs.length;i++){
        char[] array = strs[i].toCharArray();
        Arrays.sort(array);
        String s = String.valueOf(array);
        if (!map.containsKey(s)) map.put(s,new ArrayList<String>());
        map.get(s).add(strs[i]);
    }
    return new ArrayList<>(map.values());
}

leetcode128 最长连续序列

/**
 * 思路:首先使用一个HashSet用于去重,然后遍历整个set,每次都去计算从当前元素开始往后的连续
 * 序列。
 * 需要注意的是使用 !set.contains(n-1) 避免重复计算
 */
public int longestConsecutive(int[] nums) {
    int maxLen = 0;
    Set<Integer> set = new HashSet<>();
    for(int n:nums){
        set.add(n);//使用HashashSet可以去掉重复的
    }
    for(int n:set){
        if( !set.contains(n-1) ){ // 结合下面的curr+1就可以避免重复的计算
            int curr = n;
            int len = 1;
            while( set.contains(curr+1) ){
                curr += 1;
                ++ len;
            }
            maxLen = Math.max(len,maxLen);
        }
    }
    return maxLen;
}

四、链表的几道题

leetcode2 两数相加

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode n1=l1,n2=l2;
    ListNode head = new ListNode(0);
    ListNode curr = head;
    int carry = 0;
    while(n1!=null || n2!=null){
        int val1 = n1==null?0:n1.val;
        int val2 = n2==null?0:n2.val;
        int sum =  val1 + val2 + carry;
        carry = 0;
        if (sum >= 10) {
            carry = 1;
        }
        curr.next = new ListNode(sum % 10);
        curr = curr.next;
        if (n1!=null) {
            n1 = n1.next;
        }
        if (n2!=null) {
            n2 = n2.next;
        }
    }
    return head.next;
}

leetcode19 删除链表倒数第n个节点

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode node = head;
    ListNode pre = null;
    ListNode curr = head;
    //让第一个节点先走 n 步,这样到达终点时curr就是要删除的节点
    for (int i=0;i<n-1;i++){
        node = node.next;
    }
    while(node.next!=null){
        pre = curr;
        curr = curr.next;
        node = node.next;
    }
    if (pre == null) head = head.next;
    else pre.next = pre.next.next;
    return head;
}

leetcode23 合并k个升序序列

//解法1 :顺序合并
public ListNode mergeKLists(ListNode[] lists) {
    if(lists == null) {
        return null;
    }
    int k = lists.length;
    boolean over = false;
    ListNode res = new ListNode();
    ListNode node = res;
    while( true ){
        int currMin = -1;
        over = true;//标识所有链表是否都结束遍历了
        //找到下一个最小的节点
        for( int i=0;i<k;i++ ){
            if( over && lists[i]!=null ){
                over = false;
            }
            if(lists[i]!=null) {
                if(currMin == -1) {
                    currMin = i;
                }
                else {
                    currMin = lists[i].val < lists[currMin].val ? i : currMin;
                }
            }
        }
        //结束
        if (over) {
            break;
        }
        node.next = lists[currMin];
        lists[currMin] = lists[currMin].next;
        node = node.next;
    }
    return res.next;
}

//解法2:直接使用优先级队列
public ListNode mergeKLists2(ListNode[] lists) {
    if (lists == null) {
        return null;
    }
    PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(new Comparator<ListNode>() {
        @Override
        public int compare(ListNode o1, ListNode o2) {
            return Integer.compare(o1.val, o2.val);
        }
    });
    int k = lists.length;
    ListNode node = null;
    for (int i=0;i<k;i++){
        node = lists[i];
        while( node != null ){
            priorityQueue.offer(node);
            ListNode n = node;
            node = node.next;
            n.next = null; // 注意这里要将原链表断开,否则最终结果可能会导致环的出现
        }
    }
    ListNode res = new ListNode();
    node = res;
    while ( !priorityQueue.isEmpty() ){
        node.next = priorityQueue.poll();
        node = node.next;
    }
    return res.next;
}

leetcode142 环形链表

找环的入口,笔试还遇到过这题了。

/**
 * 环形链表 -- 环的入口节点
 * - 是否有环:快慢指针法
 * - 环的入口在哪?
 *      (1)记录两个快慢指针相遇的位置
 *      (2)
 */
public ListNode detectCycle(ListNode head) {
    ListNode pfast=head;
    ListNode pslow=head;
    while(pfast!=null){
        //慢指针每次走一步
        pslow = pslow.next;
        //快指针每次走两步
        pfast = pfast.next;
        if (pfast!=null) {
            pfast = pfast.next;
        }
        //快慢指针相遇,表示存在环
        if (pfast==pslow) {
            break;
        }
    }
    //若存在环
    if (pfast!=null) {
        int count = 1;
        pfast = pfast.next;
        //计算出环的大小count
        while(pfast!=pslow){
            count++;
            pfast = pfast.next;
        }
        pslow = head;
        pfast = head;
        for (int i=0;i<count;i++){
            pfast = pfast.next;
        }
        //相遇时,快指针pfast相比慢指针pslow多走了一个环的距离,因此相遇的地方就是环的入口!
        while(pfast!=pslow) {
            pslow = pslow.next;
            pfast = pfast.next;
        }
    }
    return pfast;
}

五、树的几道题

leetcode98 验证二叉搜索树

二叉搜索树的中序遍历是升序的,使用一个pre记录前一个遍历的节点

Integer pre = null;
public boolean isValidBSTCore(TreeNode node) {
    if (node==null) {
        return true;
    }
    if (!isValidBSTCore(node.left)) {
        return false;
    }
    if (pre!=null && pre>=node.val) {
        return false;
    }
    //更新pre
    pre = node.val;
    return isValidBSTCore(node.right);
}

leetcode236 二叉树最近公共祖先

/**
* 精选思路:如果当前节点root 为 p ,q 的最近公共祖先节点,那么存在三种情况:
 * (1)p , q 在root的左右子树中,并且都在异侧
 * (2)p 为 root
 * (3)q 为 root
 * 递归思路,使用后序遍历,
 *         遇到p 或者 q 时就返回该节点,
 *         当超过叶子节点还没有p,q 就返回null
 *         左右子树都返回null,则当前节点也返回null,表明当前节点不是公共祖先
 *         若其一不为null,表明要么不为最近公共祖先,但是某一个子树包含p,q 两节点
 * @param root
 * @param p
 * @param q
 * @return
 */
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root==null) {
        return null;
    }
    if(root.val==p.val || root.val==q.val) {
        return root;//遇到p 或者 q 时就直接返回该节点
    }
    //后序遍历
    TreeNode left = lowestCommonAncestor(root.left, p, q);//遍历左子树
    TreeNode right = lowestCommonAncestor(root.right, p, q);//遍历右子树
    if (left==null && right==null) {
        return null;//左右子树都不包含p , q ,返回null
    }
    if (left==null) {
        return right;//左子树中不包含p,q,则p,q肯定在右子树
    }
    if (right==null) {
        return left;//右子树中不包含p,q,则p,q肯定在左子树
    }
    return root;//最近公共祖先为此!即左右子树包含p , q
}

/**
 * 第二种解法,借助HashMap
 * 进行一次先序遍历,使用HashMap记录所有节点的父节点
 * 从其中一个节点p开始遍向上遍历,记录所有祖先;
 * 再从另外一个q开始向上遍历祖先,若某个祖先已被p访问,那这个节点就是最近公共祖先节点
 */

根据两个遍历顺序构造一棵二叉树

leetcode105 根据前序遍历与中序遍历构造二叉树

  • 前序遍历可以得到父节点
  • 中序遍历根据父节点分成左右树
  • 重复上述步骤
public TreeNode buildTree(int[] preorder, int[] inorder) {
	  if (preorder==null || inorder==null) {
	      return null;
	  }
	  return buildTreeCore(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
	}
	
	public TreeNode buildTreeCore(int[] preorder, int[] inorder,int lp,int rp,int li,int ri) {
	  if (lp>rp || li>ri){
	      return null;
	  }
	
	  TreeNode node = new TreeNode(preorder[lp]);//根节点
	
	  int i = li;//找到中序序列的中值
	  while(i<=ri && inorder[i]!=preorder[lp]){//找到中序遍历根节点
	      ++i;
	  }
	
	  node.left = buildTreeCore(preorder,inorder,lp+1,lp+i-li,li,i-1);
	  node.right = buildTreeCore(preorder,inorder,lp+i-li+1,rp,i+1,ri);
	
	  return node;
	}

根据后序遍历与中序遍历构造二叉树

  • 后序遍历也可以得到父节点
  • 中序遍历根据父节点分成左右树
  • 重复上述步骤

leetcode114 二叉树展开为链表

/**
 * 方法一
 * 先前序遍历,再转化链表
 * @param root
 */
public void flatten(TreeNode root) {

    if (root==null) {
        return ;
    }
    ArrayList<TreeNode> list = new ArrayList<>();
    travese(list,root);
    for (int i=1;i<list.size();i++){
        list.get(i-1).right = list.get(i);
        list.get(i-1).left = null;
    }
    root = list.get(0);
}
//前序遍历,记住结果
public void travese(List<TreeNode> list,TreeNode root){
    LinkedList<TreeNode> stack = new LinkedList<>();
    TreeNode node = root ;
    while(node!=null || !stack.isEmpty()){
        while(node!=null){
            list.add(node);
            stack.push(node);
            node = node.left;
        }
        node = stack.pop();
        node = node.right;
    }
}


/**
 * 方法二
 * 边前序遍历边转化链表
 * (1)进行先序遍历
 * (2)需要记住前一个遍历的节点pre,让当前节点作为前一个节点的右子节点,并且令左子节点为null
 * (3)让右子节点先入栈,左子节点再入栈。(这么做可以记住右子节点,且下次出栈时只有左子节点先出栈)
 * @param root
 */
public void flatten2(TreeNode root) {
    if (root==null) {
        return ;
    }
    LinkedList<TreeNode> stack = new LinkedList<>();
    stack.push(root);
    TreeNode pre = null;
    while(!stack.isEmpty()){
        TreeNode curr = stack.pop();
        if (pre!=null) {
            pre.left = null;
            pre.right = curr;
        }
        if (curr.right!=null){
            stack.push(curr.right);
        }
        if (curr.left!=null){
            stack.push(curr.left);
        }
        pre = curr;
    }
}

leetcode538 二叉搜索树转化为累加树

/**
 * 二叉搜索树左子树的值都小于当前节点,右子树的值都大于当前节点
 * 使用Sum记录大于当前数的节点之和
 * 使用反序中序遍历,从最大的节点开始累加即可 
 */
class Solution {
    int sum = 0;

    public TreeNode convertBST(TreeNode root) {
        if(root!=null){
            convertBST(root.right);
            sum += root.val;
            root.val = sum;
            convertBST(root.left);
        }
        return root;
    }
}

六、排序的几道题

leetcode253 会议室II

/**
 * 思路:将会议安排按照开始时间排序
 *      使用一个优先级队列作为最小堆,有一个新会议进来时去判断堆顶的最先结束的会议是否已经结束,结束了则直接进队,否则开辟新的会议室
 *      
 * 优先级队列  类比  会议室
 */

/**
 * 实现快排
 */
public static void quickSort(int[][] intervals,int left,int right){
    int l = left;
    int r = right;
    int pivot = intervals[(l+r)/2][0];
    int temp;
    while(l<r){
        while(intervals[l][0] < pivot){
            l++;
        }
        while(intervals[r][0] > pivot){
            r--;
        }
        if (l>=r) {
            break;
        }
        for(int i=0;i<2;i++){
            temp = intervals[r][i];
            intervals[r][i] = intervals[l][i];
            intervals[l][i] = temp;
        }
        if(intervals[r][0] == pivot) {
            l++;
        }
        if(intervals[l][0] == pivot) {
            r--;
        }
    }
    if (l==r){
        l++;
        r--;
    }
    if(left<r) {
        quickSort(intervals,left,r);
    }
    if(right>l) {
        quickSort(intervals,l,right);
    }
}

public static int minMeetingRooms(int[][] intervals) {

    if (intervals==null || intervals.length==0 || intervals[0].length==0) {
        return 0;
    }

    //创建最小堆,堆头是最先结束的会议
    PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return  Integer.compare(o1[1],o2[1]);
        }
    });
    int len = intervals.length;
    quickSort(intervals,0,len-1);
    priorityQueue.add(intervals[0]);
    for(int i=1;i<len;i++){
        int[] peek = priorityQueue.peek();
        //已结束的会议退出会议室(即我们的优先级队列)
        if (peek[1]<=intervals[i][0]){
            priorityQueue.poll();
        }
        priorityQueue.add(intervals[i]);
    }
    return priorityQueue.size();
}

leetcode56 合并区间

/**
 * 与253类似题目,核心思想就是如果我们按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的
 */
public int[][] merge(int[][] intervals) {

    if (intervals==null || intervals.length==0 || intervals[0].length==0) {
        return new int[0][0];
    }

    Arrays.sort(intervals, new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return Integer.compare(o1[0],o2[0]);
        }
    });
    ArrayList<int[]> list = new ArrayList<>();
    int[] temp = intervals[0];
    for(int i=1;i<intervals.length;i++){
        if(temp[1]>=intervals[i][0]){//注意 等于 时也是存在交界,需要合并!
            temp[1] = temp[1]<intervals[i][1]?intervals[i][1]:temp[1];//合并
        }else{
            list.add(temp);
            temp = intervals[i];
        }
    }
    list.add(temp);
    int[][] merge = new int[list.size()][2];
    int i = 0;
    for (int[] t : list){
        merge[i++] = t;
    }
    return merge;
}

leetcode215 数组中的第 K 个最大元素

  • 排序 或 直接使用优先级队列
 /**
 * 基于堆排
 */
public int findKthLargest(int[] nums, int k) {
    heapSort(nums);
    return nums[k-1];
}

/**
 * 堆排序
 * @param nums
 */
private void heapSort(int[] nums){
    if (nums == null) {
        return;
    }
    int len = nums.length;
    for ( int i=len/2-1;i>=0;i-- ){
        buildMinHeap(nums,i,len);
    }
    for (int i=len-1;i>0;i--){
        swap(nums,0,i);
        buildMinHeap(nums,0,i);
    }
}

/**
 * 构建最小堆
 * @param nums
 * @param i
 * @param len
 */
private void buildMinHeap(int[] nums,int i,int len){
    int temp = nums[i];
    for (int k=2*i+1;k<len;k=2*k+1){
          if ( k+1<len && nums[k+1]<nums[k] ){
              k += 1;
          }
          if( nums[k] < temp ){
              nums[i] = nums[k];
              i = k;
          }
    }
    nums[i] = temp;
}

private void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}
/**
 * 基于快排
 */
public int findKthLargest(int[] nums, int k) {
    quickSort(nums,0,nums.length-1);
    return nums[k-1];
}
/**
 * 快排
 */
private void quickSort(int[] nums,int left,int right){
    int l = left,r = right;
    int pivot = nums[(l+(r-l)/2)];
    while(l<r){
        while( nums[l] > pivot ){
            ++l;
        }
        while( nums[r] < pivot ){
            --r;
        }
        if ( l >= r ) {
            break;
        }
        swap(nums,l,r);
        if ( nums[l] == pivot ){
            --r;
        }
        if ( nums[r] == pivot ){
            ++l;
        }
    }
    if ( l == r ){
        ++l;
        --r;
    }
    if (left<r){
        quickSort(nums,left,r);
    }
    if (l<right){
        quickSort(nums,l,right);
    }
}

/**
 * 交换
 * @param nums
 * @param i
 * @param j
 */
private void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}
//使用优先级队列
public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2-o1;
        }
    });
    for (int i=0;i<nums.length;i++){
        queue.add(nums[i]);
    }
    for (int i=0;i<k-1;i++){
        queue.poll();
    }
    return queue.peek();
}

leetcode347 前K个高频元素

这里也是优先级队列的用法 —— 保证队列中存放着K个最高频的元素。

public int[] topKFrequent(int[] nums, int k) {
    int len = nums.length;
    HashMap<Integer, Integer> frequent = new HashMap<>();
    //小顶堆,用于记录最高频K个数
    PriorityQueue<int[]> minHeap = new PriorityQueue<>(new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return Integer.compare(o1[1], o2[1]);
        }
    });
    //计数每个元素的出现次数
    for (int i=0;i<len;i++){
        frequent.put(nums[i],frequent.getOrDefault(nums[i],0)+1);
    }
    for ( Map.Entry<Integer,Integer> entry : frequent.entrySet() ){
        int[] temp = {entry.getKey(), entry.getValue()};
        if (minHeap.size()<k){
            minHeap.add(temp);
        }else if (minHeap.peek()[1] < entry.getValue()){
            minHeap.poll();
            minHeap.add(temp);
        }
    }
    int[] res = new int[k];
    for (int i=0;i<k;i++){
        res[i] = minHeap.poll()[0];
    }
    return res;
}

七、设计某种数据结构

leetcode146 LRU 缓存机制

注意一个点就是初始化时,让头尾节点都指向一个没有值的节点,这样子后续插入删除时就不必去对头尾节点做多余的操作。

/**
 * 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
 * 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
 * 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。
 * 当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
 *
 *  * 总结:
 *  * 接用LinkedHashMap的思路,遇到的问题:容量不够用时,忘记删除‘头’节点的值
 *  * 删除头尾节点时,注意边界情况,头节点.next.next没有值,或者尾节点.next.netx没有值,这时候设置prev会出问题!
 *  * 双向链表,可以使用空的头尾节点,然后在中间插入各种值!~
 *
 */

/**
 * 双向链表
 */
class ListNode{
    Integer key ;
    Integer item ;
    ListNode next;
    ListNode prev;
    public ListNode(Integer item,Integer key) {
        this.item = item;
        this.key = key;
    }
    public ListNode() {
    }
}

class LRUCache {
    private ListNode head;
    private ListNode tail;
    /**
     * 用于存放每个节点链表的位置
     */
    private HashMap<Integer,ListNode> map;
    /**
     * 容量0
     */
    private final int CACHE_CAPACITY ;

    public LRUCache(int capacity) {
        CACHE_CAPACITY = capacity;
        map = new HashMap<>(CACHE_CAPACITY);
        head = new ListNode();
        tail = new ListNode();
        tail.prev = head;
        head.next = tail;
    }

    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        ListNode node = map.get(key);
        //移除
        removeNode(node);
        //插到头节点
        moveToHead(node);
        return node.item;
    }

    public void put(int key, int value) {
        ListNode node;
        if (map.containsKey(key)){
            node = map.get(key);
            node.item = value;
            //移除
            removeNode(node);
        }else{
            node = new ListNode(value,key);
            map.put(key,node);
        }
        //插到头节点
        moveToHead(node);
        if (map.size()>CACHE_CAPACITY){//超出容量移除尾节点
            map.remove(Integer.valueOf(tail.prev.key));
            removeNode(tail.prev);
        }
    }


    private void removeNode(ListNode  node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(ListNode node){
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
        node.prev = head;
    }

    private void moveToTail(ListNode node){
        tail.prev.next = node;
        node.prev = tail.prev;
        node.next = tail;
        tail.prev = node;
    }
}

八、回溯法的几道题

回溯法是深度优先搜索的一种.
其实也是一种暴力解…

result = []
def backtrack(路径, 选择列表):
	if 满⾜结束条件:
		result.add(路径)
		return
	for 选择 in 选择列表:
		做选择
		backtrack(路径, 选择列表)
		撤销选择

N皇后问题

其实就是一个全排列的问题

//记录一共有多少中排列
static int count = 0;

//第row行的皇后做选择 j = 0 - n
public static void NQueenCount(int[][] matrix, int row, int n) {
    //满⾜结束条件
    if (row == 8) {
        //result.add(路径)
        count++;
        return;
    }

    //for 选择 in 选择列表:
    for (int j = 0; j < n; j++) {
        if (isNotConflict(matrix, row, j, n)) {
            //做选择
            matrix[row][j] = 1;
            //backtrack(路径, 选择列表)
            NQueenCount(matrix, row + 1, n);
            //撤销选择
            matrix[row][j] = 0;
        }
    }

}

//判断是否发生冲突
public static boolean isNotConflict(int[][] matrix, int i, int j, int n) {
    //横向
    for (int k = 0; k < n; k++) {
        if (matrix[i][k] == 1) return false;
    }
    //纵向
    for (int k = 0; k < n; k++) {
        if (matrix[k][j] == 1) return false;
    }
    //四个对角线方向
    for (int k = i, l = j; k < n && l < n; k++, l++) {
        if (matrix[k][l] == 1) return false;
    }
    for (int k = i, l = j; k >= 0 && l >= 0; k--, l--) {
        if (matrix[k][l] == 1) return false;
    }
    for (int k = i, l = j; k >= 0 && l < n; k--, l++) {
        if (matrix[k][l] == 1) return false;
    }
    for (int k = i, l = j; k < n && l >= 0; k++, l--) {
        if (matrix[k][l] == 1) return false;
    }
    return true;
}

九、深度优先遍历的几道题

解决在 【图】中查找路径的问题

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
	Queue<Node> q; // 核⼼数据结构
	Set<Node> visited; // 避免⾛回头路
	q.offer(start); // 将起点加⼊队列
	visited.add(start);
	int step = 0; // 记录扩散的步数
	while (q not empty) {
		int sz = q.size();
		/* 将当前队列中的所有节点向四周扩散 */
		for (int i = 0; i < sz; i++) {
			Node cur = q.poll();
			/* 划重点:这⾥判断是否到达终点 */
    		if (cur is target)   return step;
            /* 将 cur 的相邻节点加⼊队列 */
            for (Node x : cur.adj()){/* cur.adj() 泛指 cur 相邻的节点 */
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
         		}	
            }
		}
    	/* 划重点:更新步数在这⾥ */
    	step++;
	}
}

leetcode301 删除无效括号(困难)

/**
 * 思路:
 * (1)首先遍历字符串,判断需要删除的左括号或者右括号的数量,分别是left_rem和right_rem  -->  所有删除最少无效括号的结果 都是 删除这个数量的括号
 * (2)回溯时则根据是否已经删除了所有多出来的括号,来判断是否添加到结果集中
 *
 * 对于普通的回溯,是不知道哪些括号放错了位置的,因为回溯时会尝试移除每一个括号,最后再去得到删除最少无效括号的结果;
 * 而优化后的回溯,已知需要移除的括号的数量,这样也就不必在加入结果集时判断删除数量的多少了。
 */
private Set<String> validExpressions = new HashSet<String>();

//深度优先遍历
private void recurse(
        String s,
        int index,
        int leftCount,
        int rightCount,
        int leftRem,
        int rightRem,
        StringBuilder expression) {

    if (index == s.length()) {
        //所有左右括号刚好匹配,加入结果集中!
        if (leftRem == 0 && rightRem == 0) {
            this.validExpressions.add(expression.toString());
        }
    } else {
        char character = s.charAt(index);
        int length = expression.length();

        //若 左括号或者右括号 需要删除的数量 大于0 时,删除当前括号
        if ((character == '(' && leftRem > 0) || (character == ')' && rightRem > 0)) {
            this.recurse(
                    s,
                    index + 1,
                    leftCount,
                    rightCount,
                    leftRem - (character == '(' ? 1 : 0),//删除当前左括号
                    rightRem - (character == ')' ? 1 : 0),//删除当前右括号
                    expression);
        }
        //回溯:不删除当前括号
        expression.append(character);

        // 不为左括号也不为右括号,直接加入结果expression中
        if (character != '(' && character != ')') {
            this.recurse(s, index + 1, leftCount, rightCount, leftRem, rightRem, expression);
        } else if (character == '(') {

            this.recurse(s, index + 1, leftCount + 1, rightCount, leftRem, rightRem, expression);

        } else if (rightCount < leftCount) {//等于右括号 且 右括号数量小于左括号

            this.recurse(s, index + 1, leftCount, rightCount + 1, leftRem, rightRem, expression);
        }

        //移除当前元素,继续回溯
        expression.deleteCharAt(length);
    }
}

public List<String> removeInvalidParentheses(String s) {
    int left = 0, right = 0;
    //找出需要删除的右括号和左括号数量
    for (int i = 0; i < s.length(); i++) {
        //计算假设需要删除的左括号数
        if (s.charAt(i) == '(') {
            left++;
        } else if (s.charAt(i) == ')') {
            // 如果遇到右括号时发现没有与之对应的最括号,那么该右括号时需要删除的
            right = left == 0 ? right + 1 : right;
            // 匹配一个右括号,假设需要删除的左括号数减1
            left = left > 0 ? left - 1 : left;
        }
    }
    this.recurse(s, 0, 0, 0, left, right, new StringBuilder());
    return new ArrayList<String>(this.validExpressions);
}

leetcode207 课程表

/**
 * 思路:
 * 找到入度为0 的课程,也就是当前可以上的课,加入队列
 * 然后出队,将以我为前置课程的其他节点(课程)的入度减一,若减完之后入度为0,则重新加入队列
 *
 * 类似BFS算法,因此使用广度优先遍历
 * @param numCourses
 * @param prerequisites
 * @return
 */
public static boolean canFinish(int numCourses, int[][] prerequisites) {
    //用于记录节点依赖关系,即记录每一门课所依赖的前置课程都有哪些
    List<List<Integer>> dependencies = new ArrayList<>();
    //用于记录入度,表示每一门课需要学习的前置课程数量
    int[] rudu = new int[numCourses];
    for (int i=0;i<numCourses;i++){
        dependencies.add(new ArrayList<>());
    }
    for (int i=0;i<numCourses;i++){
        ++rudu[prerequisites[i][0]];
        //记录我是谁的入度,学习(删掉)我之后,将这些以我为入度的节点的入度减1!
        dependencies.get(prerequisites[i][1]).add(prerequisites[i][0]);
    }
    //进行广度优先遍历的队列
    LinkedList<Integer> queue = new LinkedList<>();
    for (int i=0;i<numCourses;i++){//将入度为0的课程加入队列
        if (rudu[i]==0) {
            queue.offer(i);
        }
    }
    while (!queue.isEmpty()){
        Integer pre = queue.poll();
        --numCourses;
        for (Integer curr : dependencies.get(pre)){
            if (--rudu[curr]==0) {
                queue.addLast(curr);
            }
        }
    }
    return numCourses==0;
}

leetcode200 岛屿数量

/**
 * 使用BFS广度优先算法
 * 将二维矩阵看成是一个无向图,竖直或水平相邻的 111 之间有边相连
 * 遇到 1 时将其置为 0,并搜索与之相连的所有节点
 */
public int numIslands(char[][] grid) {
    int rowLen = 0;
    int cowLen = 0;
    int islandNums = 0;//记录岛屿个数
    if(grid==null || (rowLen=grid.length)<=0 || (cowLen=grid[0].length)<=0 ) return 0;
    for (int i=0;i<rowLen;i++){
        for (int j=0;j<cowLen;j++){
            if (grid[i][j]=='1'){
                LinkedList<Integer> queue = new LinkedList<>();
                queue.offerLast(i*cowLen+j);//这种记录二维矩阵坐标的做法可以借鉴!还有下面获取坐标的方法
                while (!queue.isEmpty()){
                    Integer poll = queue.poll();
                    //获取坐标的方法
                    int r = poll / cowLen;
                    int c = poll % cowLen;
                    if(r-1>=0 && grid[r-1][c]=='1'){
                        grid[r-1][c] = '0';
                        queue.offerLast((r-1)*cowLen+c);
                    }
                    if(r+1<rowLen && grid[r+1][c]=='1'){
                        grid[r+1][c] = '0';
                        queue.offerLast((r+1)*cowLen+c);
                    }
                    if(c-1>=0 && grid[r][c-1]=='1'){
                        grid[r][c-1] = '0';
                        queue.offerLast(r*cowLen+c-1);
                    }
                    if(c+1<cowLen && grid[r][c+1]=='1'){
                        grid[r][c+1] = '0';
                        queue.offerLast(r*cowLen+c+1);
                    }
                }
                islandNums++;//增加岛屿个数
            }
        }
    }
    return islandNums;
}

十、动态规划的几道题

留个空回头补上

十一、查找的几道题

二分查找随记

十二、栈的几道题

单调栈的几道题

单调栈

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值