面试手撕代码(3)

128.最长连续序列——hashset暴力破解+重复优化

给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 O(n)。

思路:

  • 这道题很容易想到hashset,然后遍历每一个出现的数字来统计最长序列长度;
  • 假如是数组 54367,当我们遇到 5 的时候计算一遍 567,遇到 4 又计算一遍 4567,遇到 3 又计算一遍 34567,这样会存在很多的重复计算。
  • 明显从 3 开始才是我们想要的序列,只考虑从序列最小的数开始即可
  • 当考虑 n 的时候,我们先看一看 n - 1 是否存在,如果不存在,那么从 n 开始就是我们需要考虑的序列了。否则的话,直接跳过。
public class No128 {
    public int longestConsecutive(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        for (int i : nums) {
            set.add(i);
        }
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            int num = nums[i];
            if (!set.contains(num - 1)) {
                int count = 0;
                while (set.contains(num)) {
                    count++;
                    num++;
                }
                max = Math.max(max, count);
            }
        }
        return max;
    }
}

958.二叉树的完全性检验——BFS+标志位

给定一个二叉树,确定它是否是一个完全二叉树。

思路:

  • 设置一个标志位,判断是否出现null
  • 如果出现null后都是null,返回true
  • 如果出现null后,又出现节点,直接返回false
public class No958 {
    public boolean isCompleteTree(TreeNode root) {
        if(root == null) return false;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        boolean flag = false;
        while (!queue.isEmpty()){
            TreeNode cur = queue.poll();
            if(cur == null){
                flag = true;
                continue;
            }
            if(flag){
                return false;
            }
            queue.add(cur.left);
            queue.add(cur.right);
        }
        return true;
    }
}

62. 不同路径——dp+滚筒数组优化

一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角,问总共有多少条不同的路径?

public int uniquePaths(int m, int n) {
    int[] dp = new int[n];
    Arrays.fill(dp,1);
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[j] += dp[j - 1];
        }
    }
    return dp[n - 1];
}

162.寻找峰值——未排序数组的二分法

题目要求时间复杂度到 log N,所以要从二分法考虑,能用二分法是因为我们可以根据某个条件,直接抛弃一半的元素。题目告诉我们可以返回数组中的任意一个峰顶。所以我们只要确定某一半至少存在一个峰顶,那么另一半就可以抛弃掉。

public class No162 {
	//线性
    public int findPeakElement2(int[] nums) {
        for (int i = 0; i < nums.length - 1; i++) {
            if(nums[i] > nums[i + 1]){
                return i;
            }
        }
        return nums.length - 1;
    }

	//对数
    public int findPeakElement(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left != right){
            int mid = (left + right) >>> 1;
            if(nums[mid] < nums[mid + 1]){
                left = mid + 1;
            }else{
                right = mid;
            }
        }
        return left;
    }
}

322.零钱兑换——dp

  • dp[i]表示零钱和为 i 的时候需要dp[i]个硬币才能凑出
  • 如果没有任何一种硬币组合能组成总金额,返回 -1,我们可以将dp直接随便填充一个大于amout的数(不能是Integer.MAX_VALUE,因为状态转移那一步会越界)
  • base case : dp[0] = 0,因为零钱和为0时没有一种方法可以凑出
public class No322 {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];//dp[i]表示amout为i的时候需要dp[i]个硬币才能凑出
        Arrays.fill(dp,amount + 10);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for(int coin : coins){
                if(i - coin < 0){
                    continue;
                }
                dp[i] = Math.min(dp[i],1 + dp[i - coin]);
            }
        }
        return (dp[amount] == amount + 10) ? -1 : dp[amount];
    }
}

23.合并k个升序链表——归并排序,优先队列

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

归并排序

和数组的归并排序是一样的,不断递归到底层,然后两两合并,最终时间复杂度是 O(nlog(n))

public class No23 {
    public ListNode merge(ListNode[] lists, int lo, int hi) {
        if (lo == hi) {
            return lists[lo];
        }
        int mid = (lo + hi) >>> 1;
        ListNode l1 = merge(lists, lo, mid);
        ListNode l2 = merge(lists, mid + 1, hi);
        return mergeK2Lists(l1, l2);
    }

    public ListNode mergeK2Lists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);//哨兵
        ListNode pre = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                pre.next = l1;
                l1 = l1.next;
            } else {
                pre.next = l2;
                l2 = l2.next;
            }
            pre = pre.next;
        }
        pre.next = l1 == null ? l2 : l1;
        return prehead.next;
    }
}

优先队列

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

public class No23 {
    public ListNode mergeKLists(ListNode[] lists) {
        //定义比较器
        Comparator<ListNode> cmp = new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val - o2.val;
            }
        };
        Queue<ListNode> queue = new PriorityQueue<>(cmp);
        //Queue<ListNode> queue = new PriorityQueue<>((x,y)->x.val - y.val);
        for (ListNode node : lists) {
            if (node != null) {
                queue.offer(node);
            }
        }
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while (!queue.isEmpty()) {
            cur.next = queue.poll();
            cur = cur.next;
            if (cur.next != null) {
                queue.offer(cur.next);
            }
        }
        return dummy.next;
    }
}

41.寻找第一个缺失的正数——原地哈希

给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

  • 和剑指offer第三题挺像的,思路都是一样,原地哈希
  • 在遍历数组的时候,如果当前位置的数字是 x,如果 x 在 [1,len] 范围内,则将 x 交换到指定位置也就是num[x - 1]上,用一个 while 循环再次判断新换来的数字是不是在 [1,len] 范围内
  • 如果某位置的数字不在[1,len]内,或者在交换后的现在位置数字不在[1,len]内,直接忽略,遍历下一个就好
  • 最后遍历一遍数组,第一个不满足 nums[i] != i + 1 的 i 就是我们要求的下标
public class No41 {
    public int firstMissingPositive(int[] nums) {
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            //如果已经在正确的位置上了,就不需要改
            if(nums[i] == i + 1){
                continue;
            }
            //将[1,len]范围内的数字交换到指定位置上,如果某位置的数字不在[1,len]内,或者在交换后的现在位置数字不在[1,len]内,直接忽略
            while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
                //满足在指定范围内、并且没有放在正确的位置上,才交换
                swap(nums, nums[i] - 1, i);
            }
        }
        // [1, -1, 3, 4]
        for (int i = 0; i < len; i++) {
            if (nums[i] != i + 1) {
                return i + 1;
            }
        }
        // 都正确则返回数组长度 + 1
        return len + 1;
    }

    private void swap(int[] nums, int x, int y) {
        int temp = nums[x];
        nums[x] = nums[y];
        nums[y] = temp;
    }
}

101.对称二叉树——递归

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root==null) return true;
        return recur(root.left,root.right);
    }
    public boolean recur(TreeNode left,TreeNode right){
        if(left==null&&right==null) return true;
        if(left==null||right==null) return false;
        if(left.val!=right.val) return false;
        return recur(left.left,right.right)&&recur(left.right,right.left);
    }
}

200.岛屿数量——DFS,灵活使用标记

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

  • 每碰到一个 1 ,就把相应的连在一起的 1 改成 2,并把岛屿数量加 1;
class Solution {
    public int numIslands(char[][] grid) {
        if(grid.length==0||grid[0].length==0) return 0;
        int count=0;//岛屿数量
        for(int row=0;row<grid.length;row++){
            for(int col=0;col<grid[0].length;col++){
                if(grid[row][col]=='1'){
                    count++;
                    marked(grid,row,col,grid.length,grid[0].length);
                }
            }
        }
        return count;
    }
    //把岛屿1都改成2,统计个数
    public void marked(char[][] grid,int i,int j,int rows,int cols){
        //base case
        if(i<0||j<0||i>rows-1||j>cols-1||grid[i][j]!='1'){
            return;
        }
        grid[i][j]='2';//标记这个点已经来过
        marked(grid,i-1,j,rows,cols);
        marked(grid,i+1,j,rows,cols);
        marked(grid,i,j+1,rows,cols);
        marked(grid,i,j-1,rows,cols);
    }
}

155.最小栈——数据栈+单调栈

class MinStack {
    Stack<Integer> stack1;//数据栈
    Stack<Integer> stack2;//辅助栈
    /** initialize your data structure here. */
    public MinStack() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    public void push(int x) {
        stack1.add(x);
        if(stack2.isEmpty() || stack2.peek()>=x){
            stack2.add(x);
        }
    }
    public int pop() {
        if(stack1.isEmpty()){
            throw new RuntimeException("空栈,操作非法");
        }else{
            int pop = stack1.pop();
            if(pop == stack2.peek()){
                stack2.pop();
            }
            return pop;
        }
    }

    public int top() {
        if(!stack1.isEmpty()){
            return stack1.peek();
        }
        throw new RuntimeException("空栈,操作非法");
    }

    public int getMin() {
        if(!stack2.isEmpty()){
            return stack2.peek();
        }
        throw new RuntimeException("空栈,操作非法");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值