Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

心得:
1.梳理思路
2.代码实现落地还有段距离,需要考个各种情况和边界情况的处理
3.画的图要表达出变化的逻辑,然后抽象出变化的规则
4.理解后看图写代码

作业题

(Facebook、字节跳动、微软在半年内面试中考过)
  有序数组,隐含重复数据是相邻的
  画出图中的变化,画图写代码
  双指针中快指针从1开始遍历

class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums == null || nums.length == 0) {
            return 0;
        }
        int i = 0;
        for (int j = 1; j < nums.length; j++) {
            if(nums[i] != nums[j]) {
                nums[++i] = nums[j];
            } 
        }
        return ++i;
    }
}

(微软、亚马逊、PayPal 在半年内面试中考过)

  暴力方法 时间复杂度O(k*n),空间复杂度O(1)

class Solution {
    public void rotate(int[] nums, int k) {
        for (int i = 1; i <= k; i++) {
            int temp = nums[nums.length -1];
            for (int j = nums.length - 1; j > 0; j--) {
                nums[j] = nums[j-1];
            }
            nums[0] = temp;
        }
    }
}

  使用额外数组方式 时间复杂度O(n),空间复杂度O(k)
  直接对比寻找最后移动的k位后下标的对应关系
    a.移动位数>数组长度
    b.移动位数<=数组长度

class Solution {
    public void rotate(int[] nums, int k) {
        int bits = k % nums.length;
        if(bits > 0) {
            int[] tempNums = new int[bits];
            int j = 0;
            for (int i = nums.length - bits; i < nums.length; i++) {
                tempNums[j++] = nums[i];
            }
            for (int i = nums.length -1; i >= bits; i--) {
                nums[i] = nums[i-bits];
            }
            for (int i = 0; i < bits; i++) {
                nums[i] = tempNums[i];
            }
        }
    }
}

  环状替换 时间复杂度O(n),空间复杂度O(1)

  do while循环写法
  减少变量的定义 k = k % nums.length;
  下标0移动多次k位后还会返回到原地

public class Solution {
    public void rotate(int[] nums, int k) {
        k = k % nums.length;
        int count = 0;
        for (int start = 0; count < nums.length; start++) {
            int current = start;
            int prev = nums[start];
            do {
                int next = (current + k) % nums.length;
                int temp = nums[next];
                nums[next] = prev;
                prev = temp;
                current = next;
                count++;
            } while (start != current);
        }
    }
}

  while循环写法

public class Solution {
    public void rotate(int[] nums, int k) {
        k = k % nums.length;
        int count = 1;
        for (int start = 0; count < nums.length; start++) {
            int current = start;
            int next = (current + k) % nums.length;
            int prev = nums[start];
            int temp = nums[next];
            while (start != next) {
                temp = nums[next];
                nums[next] = prev;
                prev = temp;
                current = next;
                count++;
                next = (current + k) % nums.length;
            }
            nums[start] = temp; 
        }
    }
}

  反转数组方式 时间复杂度O(n),空间复杂度O(1)
  反转数组
class Solution {
    public void rotate(int[] nums, int k) {
        // 3次数组反转
        // 1次全部反转
        // 2次反转下标0至k-1的数组
        // 3次反转下标k至nums.length-1的数组
        k = k % nums.length;
        reverse(nums, 0, nums.length-1);
        reverse(nums, 0, k-1);
        reverse(nums, k, nums.length-1);
    }

    public void reverse(int[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }
}

(亚马逊、字节跳动在半年内面试常考)
迭代法
时间复杂度O(n+m) 空间复杂度O(1)
犯错地方:
1.边界处理思路未捋清楚 2.三元运算符写错

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 1.添加哨兵节点
        // 2.合并两个链表 处理剩下集合非null的节点
        // 3.剔除哨兵节点
        ListNode prehead = new ListNode();
        ListNode temphead = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prehead.next = l1;
                l1 = l1.next;
            } else {
                prehead.next = l2;
                l2 = l2.next;
            }
            prehead = prehead.next;
        }
        prehead.next = (l1 != null ? l1 : l2);
        temphead = temphead.next;
        return temphead;
    }
}

递归法
时间复杂度O(n+m) 空间复杂度O(n+m)

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 1.终止条件
        // 2.递推公式
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        if (l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
        
    }
}

(Facebook 在半年内面试常考)
暴力方式
Arrays.sort为快速排序 所以时间复杂度O((n+m)log(n+m)) 空间复杂度O(1)

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        // 暴力方式
        // 1.nums2并入nums1尾部
        // 2.nums1排序
        System.arraycopy(nums2, 0, nums1, m, n);
        Arrays.sort(nums1);
    }
}

双指针方式 从前往后
时间复杂度O(m+n) 空间复杂度O(m)
==学习之处:==
1.并入数组的下标与两个数组下标的对应关系
2.学习熟练使用系统库System.arraycopy

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        // 使用额外数组 双指针从前往后遍历
        // 1.新建一个数组nums1_copy来存放nums1
        // 2.while循环将nums1_copy和nums2并入nums1
        // 3.处理为剩余数组中的元素并入nums1
        int[] nums1_copy = new int[m];
        System.arraycopy(nums1, 0, nums1_copy, 0, m);
        int p1 = 0, p2 = 0;
        while ((p1 < m) && (p2 < n)) {
            nums1[p1 + p2] = (nums1_copy[p1] <= nums2[p2]) ? nums1_copy[p1++] : nums2[p2++];
        }
        if (p1 < m) {
            System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2);
        } else {
            System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2);
        }
    }
}

双指针 从后往前的方式
时间复杂度O(m+n),时间复杂度O(1)

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        // 双指针 从后往前方式 
        // 1.while循环把nums1和nums2从大到小放入nums1的尾部
        // 2.考虑最后可能遗漏的nums2中的元素
        int p1 = m-1, p2 = n-1, p = nums1.length -1;
        while ((p1 >= 0) && (p2 >= 0)) {
            nums1[p--] = (nums1[p1] <= nums2[p2]) ? nums2[p2--] : nums1[p1--];
        }
        System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
    }
}

(亚马逊、字节跳动、谷歌、Facebook、苹果、微软在半年内面试中高频常考)
暴力解法
时间复杂度O(n^2),时间复杂度O(1)
==学习两遍for循环的写法==

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 暴力方式
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] == target - nums[i]) {
                    return new int[] {i, j};
                }
            }
        }
        return null;
    }
}

两遍哈希表法
时间复杂度O(n),空间复杂度O(n)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 两遍哈希表法
        // 1遍存入哈希表
        // 2遍查找哈希表
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], i);
        }
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement) && i != map.get(complement)) {
                return new int[] {i, map.get(complement)};
            }
        }
        return null;
    }
}

一遍哈希表法
时间复杂度O(n),空间复杂度O(n)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 一遍哈希表法
        // 1遍查找哈希表
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] {i, map.get(complement)};
            }
            map.put(nums[i], i);
        }
        return null;
    }
}

(Facebook、亚马逊、苹果在半年内面试中考过)
标记删除法

双指针法
时间复杂度O(n), 空间复杂度O(1)
犯错处:
未及时考虑到数组为null,或者数据个数为1的情况

class Solution {
    public void moveZeroes(int[] nums) {
        // 双指针法
        // 遍历一遍
        if (nums == null || nums.length < 2) {
            return;
        }
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                int temp = nums[j];
                nums[j++] = nums[i];
                nums[i] = temp;
            }
        }
    }
}

暴力解法:使用额外数组
时间复杂度O(n),空间复杂度O(n)

class Solution {
    public void moveZeroes(int[] nums) {
        // 暴力解法
        // 使用额外数组
        int[] result = new int[nums.length];
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                result[j++] = nums[i];
            }
        }
        for (; j < nums.length; j++) {
            result[j] = 0;
        }
        System.arraycopy(result, 0, nums, 0, nums.length);
        // nums = result;
    }
}

(谷歌、字节跳动、Facebook 在半年内面试中考过)
题意都没理解
==学习到:int[]初始化时会填充0==

class Solution {
    public int[] plusOne(int[] digits) {
        for (int i = digits.length - 1; i >= 0; i--) {
            digits[i]++;
            digits[i] %= 10;
            if (digits[i] != 0) {
                return digits;
            }
        }
        int[] result = new int[digits.length + 1];
        result[0] = 1;
        // int[] 初始化时会填充0,所以可以省略搬移操作
        // System.arraycopy(digits, 0, result, 1, digits.length);
        return result;
    }
}

(Facebook 在 1 年内面试中考过)
数组实现的循环队列的改造实现
时间复杂度O(1),空间复杂度O(1)

class MyCircularDeque {

    // 1.基于双指针数组实现的循环队列的改造
    // 2.循环队列会浪费一个存储空间
    // 3.队空情况 head == tail
    // 4.队满 (tail + 1) % capacity == head
    private int[] array;
    private int capacity;
    private int head;
    private int tail;

    /** Initialize your data structure here. Set the size of the deque to be k. */
    public MyCircularDeque(int k) {
        capacity = k + 1;
        array = new int[capacity];
    }
    
    /** Adds an item at the front of Deque. Return true if the operation is successful. */
    public boolean insertFront(int value) {
        if (isFull()) {
            return false;
        }
        head = (head - 1 + capacity) % capacity;
        array[head] = value;
        return true;
    }
    
    /** Adds an item at the rear of Deque. Return true if the operation is successful. */
    public boolean insertLast(int value) {
        if (isFull()) {
            return false;
        }
        array[tail] = value;
        tail = (tail + 1) % capacity;
        return true;
    }
    
    /** Deletes an item from the front of Deque. Return true if the operation is successful. */
    public boolean deleteFront() {
        if (isEmpty()) {
            return false;
        }
        head = (head + 1) % capacity;
        return true;
    }
    
    /** Deletes an item from the rear of Deque. Return true if the operation is successful. */
    public boolean deleteLast() {
        if (isEmpty()) {
            return false;
        }
        tail = (tail - 1 + capacity) % capacity;
        return true;
    }
    
    /** Get the front item from the deque. */
    public int getFront() {
        if (isEmpty()) {
            return -1;
        }
        return array[head];
    }
    
    /** Get the last item from the deque. */
    public int getRear() {
        if (isEmpty()) {
            return -1;
        }
        return array[(tail - 1 + capacity) % capacity];
    }
    
    /** Checks whether the circular deque is empty or not. */
    public boolean isEmpty() {
        return head == tail;
    }
    
    /** Checks whether the circular deque is full or not. */
    public boolean isFull() {
        return (tail + 1) % capacity == head;
    }
}

/**
 * Your MyCircularDeque object will be instantiated and called as such:
 * MyCircularDeque obj = new MyCircularDeque(k);
 * boolean param_1 = obj.insertFront(value);
 * boolean param_2 = obj.insertLast(value);
 * boolean param_3 = obj.deleteFront();
 * boolean param_4 = obj.deleteLast();
 * int param_5 = obj.getFront();
 * int param_6 = obj.getRear();
 * boolean param_7 = obj.isEmpty();
 * boolean param_8 = obj.isFull();
 */

优化掉取余操作
时间复杂度O(1),空间复杂度O(1)
==通过引入计数size来判断队空和队满,没有再浪费数组一个内存空间==

class MyCircularDeque {

    int[] arr;
    int size;
    int front;
    int last;

    public MyCircularDeque(int k) {
        if (k < 0) {
            throw new IllegalArgumentException("Deque size is less than 0!");
        }
        this.arr = new int[k];
        this.size = 0;
        this.front = 0;
        this.last = 0;
    }

    public boolean insertFront(int value) {
        if (size == arr.length) {
            return false;
        }
        if (front == 0) {
            arr[arr.length - 1] = value;
            front = arr.length - 1;
        } else {
            arr[--front] = value;
        }
        size ++;
        return true;
    }

    public boolean insertLast(int value) {
        if (size == arr.length) {
            return false;
        }
        arr[last] = value;
        size ++;
        last = (last == arr.length - 1) ? 0 : last + 1;
        return true;
    }

    public boolean deleteFront() {
        if (size == 0) {
            return false;
        }
        front = (front == arr.length - 1) ? 0 : front + 1;
        size--;
        return true;
    }

    public boolean deleteLast() {
        if (size == 0) {
            return false;
        }
        if (last == 0) {
            last = arr.length - 1;
        } else {
            last --;
        }
        size --;
        return true;
    }

    public int getFront() {
        if (size == 0) {
            return -1;
        }
        return arr[front];
    }

    public int getRear() {
        if (size == 0) {
            return -1;
        }
        return last == 0 ? arr[arr.length - 1] : arr[last - 1];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return size == arr.length;
    }
}

(亚马逊、字节跳动、高盛集团、Facebook 在半年内面试常考)

解析题

散列表+双向链表
put、get时间复杂度O(1), 空间复杂度O(capacity)

class LRUCache {

    class Node {
        int key, value;
        Node previous;
        Node next;
        public Node(){}
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private Map<Integer, Node> cache = new HashMap<>();
    private int capacity;
    private int size;
    private Node head;
    private Node tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.previous = head;
    }
    
    public int get(int key) {     
        // 1.存在将该节点置于头部
        // 2.不存在返回-1
        Node node = cache.get(key);
        if (node != null) {
            // 删除节点node
            // node节点添加到头部
            removeNode(node);
            addNodeToHead(node);
            return node.value;
        }
        return -1;
    }
    
    public void put(int key, int value) {
        // 1.cache中不存在 size < capacity 直接创建节点插入头部,size == capacity删除尾节点,创建节点插入头部
        // 2.cache中存在, 节点更新value,将节点移至头部
        Node node = cache.get(key);
        if (node == null) {
            node = new Node(key, value);
            if (size == capacity) {
                removeTail();
            }
            cache.put(key, node);
            addNodeToHead(node);
        } else {
            node.value = value;
            removeNode(node);
            addNodeToHead(node);
        }
    }

    private void addNodeToHead(Node node) {
        node.next = head.next;
        head.next.previous = node;
        head.next = node;
        node.previous = head;
        size++;
    }

    private void removeNode(Node node) {
        node.previous.next = node.next;
        node.next.previous = node.previous;
        size--;
    }

    private void removeTail() {
        cache.remove(tail.previous.key);
        tail.previous.previous.next = tail;
        tail.previous = tail.previous.previous;
        size--;
    }

}

(腾讯、百度、字节跳动在近半年内面试常考)

(阿里巴巴、腾讯、字节跳动在半年内面试常考)
递归方式
递归树时间复杂度O(2^n), 空间复杂度O(n)
==错误处:空间复杂度错误分析为O(2^n)==
该方式提交失败,运行超时

class Solution {
    public int climbStairs(int n) {
        // 递推公式 f(n) = f(n-1) + f(n-2)
        // 终止条件 f(1) = 1; f(2) = 2
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        return climbStairs(n-1) + climbStairs(n-2);
    }
}

递归方式进行剪枝优化
时间复杂度O(n), 空间复杂度O(n)

class Solution {
    
    private Map<Integer, Integer> cache = new HashMap();

    public int climbStairs(int n) {
        // 递推公式 f(n) = f(n-1) + f(n-2)
        // 终止条件 f(1) = 1; f(2) = 2
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        int f1 = 0, f2 = 0;
        if (cache.get(n-1) != null) {
            f1 = cache.get(n-1);
        } else {
            f1 = climbStairs(n-1);
            cache.put(n-1, f1);
        }
        if (cache.get(n-2) != null) {
            f2 = cache.get(n-2);
        } else {
            f2 = climbStairs(n-2);
            cache.put(n-2, f2);
        }
        return f1 + f2;
    }
}

(国内、国际大厂历年面试高频老题)

(字节跳动、亚马逊在半年内面试常考)
迭代 虚构一个前驱节点
时间复杂度O(n),空间复杂度O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode previous = null;
        ListNode current = head;
        while (current != null) {
            ListNode temp = current.next;
            current.next = previous;
            previous = current;
            current = temp;
        }
        return previous;
    }
}

递归
递归树的高度:时间复杂度O(n),空间复杂度(n)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // 1.终止条件 head.next == null
        // 2.递推公式 尾部反转 f(head) = f(head.next);
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

(阿里巴巴、字节跳动在半年内面试常考)

(阿里巴巴、字节跳动、腾讯在半年内面试常考)

(字节跳动、猿辅导在半年内面试常考)

(亚马逊、JPMorgan 在半年内面试常考)

(亚马逊在半年内面试常考)

(亚马逊、微软、字节跳动在半年内面试中考过)

(亚马逊在半年内面试常考)

预习题