算法题题型总结

二叉树题型

解法综述:二叉树的解法,基本上都是依赖遍历,再加上递归的思路来做的。那递归又分为深度优先和广度优先。深度优先算法,前序,中序,后序。广度优先,利用先进先出队列,一层一层的遍历完整颗树。

深度优先

1. 求二叉树的最大深度

class Solution {
    public int maxDepth(TreeNode root) {
        return deep(root, 0);
    }

    public int deep(TreeNode node, int deep){
        if (node == null) {
            return deep;
        }
        return Math.max(deep(node.left, deep + 1), deep(node.right, deep + 1));
    }
}

2. 是否是相同的树

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        return isSame(p, q);
    }

    public boolean isSame(TreeNode p, TreeNode q){
        if (p == null && q == null) {
            return true;
        }
        if ((p == null && q != null) || (p != null && q == null)) {
            return false;
        }
        return p.val == q.val && isSame(p.left, q.left) && isSame(p.right, q.right);
    }

}

3. 求根节点到叶节点数字之和

class Solution {
    public int sumNumbers(TreeNode root) {
        return sum(root, 0);
    }

    public int sum(TreeNode node, int sum){
        if (node == null) {
            return 0;
        }
        if (node.left == null && node.right == null) {
            return 10*sum + node.val;
        }
        
        return sum(node.left,node.val + sum * 10) + sum(node.right, node.val + sum * 10);
        
    }
}

广度优先

1. 填充每个节点的下一个右侧节点指针

class Solution {
    public Node connect(Node root) {
        if (root == null) {
            return null;
        }
        LinkedList<Node> list = new LinkedList<>();
        list.add(root);
        while(list.size()!=0){
            int size = list.size();
            Node pre = null;
            for (int i=0; i<size; i++) {
                Node cur = list.poll();
                if (pre != null) {
                    pre.next = cur;
                }

                if (cur.left != null) {
                    list.add(cur.left);
                }

                if (cur.right != null) {
                    list.add(cur.right);
                }
                pre = cur;
            }
        }
        return root;
    }
}

2. 二叉树的右视图

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }
        LinkedList<TreeNode> list = new LinkedList<>();
        list.add(root);
        List<Integer> resList = new ArrayList<>();
        
        while(!list.isEmpty()){
            int size = list.size();
            for(int i=0; i<size; i++){
                TreeNode node = list.poll();
                if (node.left != null) {
                    list.add(node.left);
                }
                if (node.right != null) {
                    list.add(node.right);
                }
                
                if(i==(size-1)){
                    resList.add(node.val);
                }
            }
            
        }
        return resList;
    }
}

3. 二叉树的层平均值

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        return average(root);
    }
    //offer -- add 加在队列的尾部
    //poll -- 从队列的头部取元素
    //pop -- 从队列的头部取元素
    //push -- 放在队列头部
    public List<Double> average(TreeNode root){
        List<Double> res = new ArrayList<Double>();
        LinkedList<TreeNode> list = new LinkedList<TreeNode>();
        list.add(root);
        while(list.size() != 0){
            int size = list.size();
            double sum = 0.0;
            for (int i=0; i<size; i++) {
                TreeNode node = list.poll();
                sum = sum + node.val;
                if (node.left != null) {
                    list.add(node.left);
                }
                if (node.right != null) {
                    list.add(node.right);
                }
            }
            res.add(sum/size);
        }
        return res;
    }
}

二分查找

解法综述:首先要基于一个有序数组或者是要局部有序的场景,才能比较适合实用二分查找。然后需要通过数组的左边界和右边界确定数组的中间值,再与目标值进行对比。然后不断地调整左右边界,直到左边界大于右边界。

1. 搜索插入位置

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length -1;
        int mid = 0;
        int ans = nums.length;
        while(left<=right){
            mid = left + (right - left)/2;
            if (target <= nums[mid]) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
}

2. 寻找峰值(这就是一个局部有序的场景)

class Solution {
    public int findPeakElement(int[] nums) {
        int n = nums.length;
        int left = 0, right = n - 1, ans = -1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (compare(nums, mid - 1, mid) < 0 && compare(nums, mid, mid + 1) > 0) {
                ans = mid;
                break;
            }
            if (compare(nums, mid, mid + 1) < 0) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }
    int[] get(int[] nums, int a){
        if (a < 0 || a > nums.length - 1) {
            return new int[]{0,0};
        }
        return new int[]{1, nums[a]};
    }
    public int compare(int[] nums, int a, int b){
        int[] ar = get(nums, a);
        int[] br = get(nums, b);
        if (ar[0] > br[0]) {
            return 1;
        } else if (ar[0] == br[0] ) {
            if (ar[1] > br[1]) {
                return 1;
            } 
            else if (ar[1] == br[1]) {
                return 0;
            } else {
                return -1;
            }
        } else {
            return -1;
        }
    }
}

1. 数组中k个最大元素(这个使用冒泡排序也是可以做的,复杂度k*n; 堆的复杂度 k*logn,)

class Solution {
    public int findKthLargest(int[] nums, int k) {
        //第一次大顶堆要完整构建
        //第k-1次大顶堆,使用局部构造大顶堆的方式进行
        //构建好了大顶堆要,交换,并且长度要减1
        //剔除到第k次时,就是第k个最大值

    int length = nums.length;
    buildMaxHeap(nums, length);
    swap( 0, length - 1, nums);
    length = length - 1;
    for(int i =0 ; i<k-1;i++){
        buildSubMaxHeap(nums, 0,length);
        swap( 0, length - 1, nums);
        length = length - 1;
    }
    return nums[nums.length-k];
    }

    void buildMaxHeap(int[] nums, int length){
        int start = length/2 - 1;
        for (int i = start ; i >=0; i--) {
            buildSubMaxHeap(nums, i, length);
        }
    }

    void buildSubMaxHeap(int[] nums, int pos,int length){
        int left = 2 * pos + 1;
        int right = 2 * pos + 2;
        int large = pos;
        if (left<length && nums[left] > nums[large]) {
            large = left;
        }
        if (right<length && nums[right] > nums[large]) {
            large = right;
        }
        if (large != pos) {
            swap(large, pos, nums);
            buildSubMaxHeap(nums, large, length);
        }
    }

    void swap(int a, int b , int[] nums){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

链表

解法综述:

        1. 单链表,有两个指针,一个快,一个慢。或者一个pre,一个cur

        2. 双链表,有两个指针,一个快,一个慢

        3. 使用递归 + map的方式来解

        4. 需要使用3个指针,pre,cur,next

        5. LRU缓存-涉及到的数据结构很多,最好还是使用LinkedHashMap实现

        6. 只使用一个cur,但是会使用cur.next, cur.next.next, 这种方式适合处理相邻元素的问题

单链表,有两个指针,一个快,一个慢

1. 环形链表

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode first = head;
        ListNode second = head.next;

        while (second != null) {
            if (first == second) {
                return true;
            }
            first = first.next;
            if (second.next != null) {
                second = second.next.next;
            } else {
                second = second.next;
            }
        }
        return false;
    }
}

2. 删除链表的倒数第n个节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode newHead = new ListNode();
        newHead.next = head;
        ListNode fast = head;
        ListNode slow = head;
        for (int i=1; i<n; i++) {
            if (fast != null) {
                fast = fast.next;
            } else {
                return newHead.next;
            } 
        }
        ListNode pre = newHead;
        while(fast.next!=null){
            fast=fast.next;
            slow = slow.next;
            pre = pre.next;
        }
        pre.next = slow==null?null:slow.next;
        return newHead.next;
    }
}

3. 旋转链表

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        ListNode f = head;
        ListNode s = head;
        int l = 0;
        ListNode cur = head;
        while(cur!=null){
            l++;
            cur = cur.next;
        }
        if (l==0) {
            return head;
        }
        int nk = k%l;
        if (l==0 || l==1 || nk==0) {
            return head;
        }
        for (int i=1; i<nk; i++) {
            f = f.next;
        }
        ListNode newHead = new ListNode();
        newHead.next = head;
        ListNode pre = newHead;
        while(f.next != null){
            f = f.next;
            s = s.next;
            pre = pre.next;
        }
        pre.next = null;

        f.next = head;
        return s;
    }
}

删除重复节点类型

分两种情况:一种是将重复元素,全部删除,这种cur要从空节点开始。 一种是将有重复的节点删除到只剩一个节点,这种情况cur应该从head开始。

1.  删除排序链表中的重复元素 II

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode newHead = new ListNode();
        newHead.next = head;
        ListNode cur = newHead;
        while(cur.next != null && cur.next.next != null){
            if (cur.next.val == cur.next.next.val) {
                int val = cur.next.val;
                cur.next = cur.next.next.next;
                while (cur.next != null && cur.next.val == val){
                    cur.next = cur.next.next;
                }
            } else{
                cur = cur.next;
            }
        }
        return newHead.next;
    }
}

双链表,有两个指针,一个快,一个慢

1. 合并两个有序链表

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode head = new ListNode();
        ListNode tail = head; 
        while(list1 != null && list2 != null){
            if (list1.val >= list2.val) {
                tail.next = list2;
                tail = tail.next;

                list2 = list2.next;
                
            } else
            {
                tail.next = list1;
                tail = tail.next;
                list1 = list1.next;
            }
        }
        ListNode sub = list1 != null?list1 : list2;
        tail.next = sub;
        return head.next;
    }
}

2. 两数相加

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode p1 = l1;
        ListNode p2 = l2;
        int yushu = 0;
        int zhengshu = 0;
        ListNode tail = p1;
        while (p1 != null || p2 != null) {
            int v1 = p1 != null? p1.val:0;
            int v2 = p2 != null? p2.val:0;
            yushu = (v1 + v2 + zhengshu)%10;
            tail.val = yushu;
            zhengshu = (v1 + v2 + zhengshu)/10;   
            if(p1 != null){
               p1 = p1.next;
            }
            if (p2 != null) {
                p2 = p2.next; 
            }
            ListNode notNull = (p1 !=null)? p1 : p2;
            if (notNull != null) {
                tail.next = notNull;
                tail = tail.next;
            }

        }

        if (zhengshu != 0) {
                ListNode last = new ListNode();
                last.val = zhengshu;
                tail.next = last;
            }
        return l1;
    }
}

使用递归 + map的方式来解

1. 随机链表的复制

class Solution {
    Map<Node, Node> cachedNode = new HashMap<Node, Node>();

    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        if (!cachedNode.containsKey(head)) {
            Node headNew = new Node(head.val);
            cachedNode.put(head, headNew);
            headNew.next = copyRandomList(head.next);
            headNew.random = copyRandomList(head.random);
        }
        return cachedNode.get(head);
    }
}

需要使用3个指针,pre,cur,next

1. 翻转链表

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        
        ListNode cur = head;
        ListNode pre = new ListNode();
        pre.next = cur;
        for (int i = 0; i<left-1;i++) {
                cur = cur.next;
                pre = pre.next;
        }

        for (int i=0; i<right - left; i++) {
            ListNode next = cur.next;
            cur.next = next.next;
            next.next = pre.next;
            pre.next = next;
        }
        return head;

    }
}

LRU缓存-涉及到的数据结构很多,最好还是使用LinkedHashMap实现

public class Test {
    static class LRU extends LinkedHashMap<String, Object>{

        private int capability;

        public LRU(int capability){
            this.capability = capability;
        }

        @Override
        protected boolean removeEldestEntry(HashMap.Entry eldest) {
            return this.size() > capability;
        }

    }
    public static void main(String[] args) {
        Test t = new Test();
        LRU lru = new LRU(3);

        lru.put("2","2");
        lru.put("1","1");
        lru.put("3","3");
        for (Map.Entry<String, Object> entry : lru.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        System.out.println("=====1=====");
        lru.put("4","4");
        for (Map.Entry<String,Object> entry : lru.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        System.out.println("====2======");
        lru.remove("1");
        lru.put("1","1");
        for (Map.Entry<String,Object> entry : lru.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        System.out.println("=====3=====");
        lru.put("5","5");
        for (Map.Entry<String,Object> entry : lru.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

        int[] ints = {1, 2};
        int[] arr = {1,2,3};
        int[] arr2 = new int[3];
        int[] arr3 = new int[]{1,2,3,4,5};
    }
}

只使用一个cur,但是会使用cur.next, cur.next.next, 这种方式适合处理相邻元素的问题

1. 删除排序列表中的重复元素,全部删除

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode newHead = new ListNode();
        newHead.next = head;
        ListNode cur = newHead;
        while(cur.next != null && cur.next.next != null){
            if (cur.next.val == cur.next.next.val) {
                int val = cur.next.val;
                cur.next = cur.next.next.next;
                while (cur.next != null && cur.next.val == val){
                    cur.next = cur.next.next;
                }
            } else{
                cur = cur.next;
            }
        }
        return newHead.next;
    }
}

数组

解法综述:

        1. 使用双指针的方式来解:

                a. 滑动窗口 

                b. 尾指针 + check指针

                c. 快慢指针,用在股票中,如果是上升趋势就累加

                d. 三指针

        2. 数组如何轮转

        3. 利用数组做跳跃玩法

        4. 整数转罗马数字

        5. 翻转字符串中的单词

        6. 加油站

滑动窗口 

1. 长度最小的子数组(总和大于目标值)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int first = 0;
        int second = 0;
        int min_length = Integer.MAX_VALUE;
        int total_val = nums[first];
        while(second < nums.length){
            if (total_val >= target) {
                min_length = Math.min(min_length, second - first+1);
                total_val =  total_val - nums[first];
                first++;
            } else {
                second++;
                if (second < nums.length) {
                    total_val = total_val + nums[second];
                }
            }
        }
        return min_length == Integer.MAX_VALUE ? 0 : min_length;
    }
}

2. 无重复字符的最长子串

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int first =0;
        int second = 0;
        int length = s.length();
        int max_l = 0;
        Map<Character, Integer> map = new HashMap<>();
        while(second<length){
            Character ch = s.charAt(second);
            if (map.containsKey(ch)) {
                int pos = map.get(ch);
                while (first <= pos) {
                    map.remove(s.charAt(first));
                    first++;
                }
                map.put(ch, second);
            } else {
                map.put(ch, second);
                max_l = Math.max(second - first + 1, max_l);  
            }
            second++;
        }
        return max_l;
    }
}

尾指针 + check指针

删除排序数组中的重复项 II

class Solution {
    public int removeDuplicates(int[] nums) {
 int n = nums.length;
        if (n <= 2) {
            return n;
        }
        int slow = 2, fast = 2;
        while (fast < n) {
            if (nums[slow - 2] != nums[fast]) {
                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }
        return slow;
    }
}

快慢指针,用在股票中,如果是上升趋势就累加

股票买卖的最佳时机

class Solution {
    //思路:马后炮,知道第二天会涨, 所以前一天会买
    public int maxProfit(int[] prices) {
        if (prices.length == 1) {
            return 0;
        }

        int slow = 0;
        int fast = 1;
        int total_profit = 0;
        while(fast < prices.length){
            if (prices[fast] > prices[slow]) {
                total_profit = total_profit + prices[fast] - prices[slow];
            }
            fast++;
            slow++;
        }
        return total_profit;
    }
}

数组如何轮转

轮转数组

class Solution {
    //思路很巧妙:将翻转问题转为,全局倒序,再局部正序。 
    public void rotate(int[] nums, int k) {
        int real_k = k % nums.length ;
        
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, real_k - 1);
        reverse(nums, real_k, nums.length - 1);
    }

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

利用数组做跳跃玩法

只能跳到指定位置

class Solution {
    //思路:
    //1. 确定可以跳到的最大位置,如果没有变大就是false。
    //2. 如果可以变大就继续跳,直到最尾部,那就是true
    public boolean canJump(int[] nums) {
        if (nums.length == 1) {
            return true;
        }
        int cur = 0;
        int tail = cur + nums[cur];
        // if (nums[cur] == 0) {
        //     return false;
        // }
        while (cur<=tail) {
            int pos = nums[cur] + cur;
            if (pos > tail) {
                tail = pos;
            }
            if (tail >= nums.length - 1) {
                return true;
            }
            cur++;
        }
        return false;
    }
}

可以在一定范围内随意跳

class Solution {
    //思路:
    //1. 确定可以跳到的最大位置,如果没有变大就是false。
    //2. 如果可以变大就继续跳,直到最尾部,那就是true
    public boolean canJump(int[] nums) {
        if (nums.length == 1) {
            return true;
        }
        int cur = 0;
        int tail = cur + nums[cur];
        // if (nums[cur] == 0) {
        //     return false;
        // }
        while (cur<=tail) {
            int pos = nums[cur] + cur;
            if (pos > tail) {
                tail = pos;
            }
            if (tail >= nums.length - 1) {
                return true;
            }
            cur++;
        }
        return false;
    }
}

整数转罗马数字

class Solution {
    int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    String[] symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};

    public String intToRoman(int num) {
        StringBuilder roman = new StringBuilder();
        for (int i = 0; i < values.length; ++i) {
            int value = values[i];
            String symbol = symbols[i];
            while (num >= value) {
                num -= value;
                roman.append(symbol);
            }
            if (num == 0) {
                break;
            }
        }
        return roman.toString();
    }
}

反正字符串中的单词

class Solution {
    public String reverseWords(String s) {
        s = s.trim();
        String[] words = s.split("\\s+");
        StringBuilder res = new StringBuilder();
        for (int i= words.length - 1; i>=0; i--) {
            res.append(words[i]);
            res.append(" ");
        }
        return res.toString().trim();
    }
}

加油站

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
       //思路:转换成累计值最大的点
       int sum = 0;
       int max = 0;
       int ans = 0;
       for(int i=gas.length -1; i>=0; i--){
            sum = sum + gas[i] - cost[i];
            max = Math.max(max, sum);
            if (sum >= max) {
                ans = i;
            }
       }
       return sum >= 0 ? ans : -1;
    }
}

求三数之和

关键:要先排序。排序的优点:1. 可以快速过滤重复值。2. 更深层的遍历,范围会更小

class Solution {
    //-4,-1,-1,0,1,2
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        for(int first=0; first<nums.length-2;first++){
            if(first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            int third = nums.length - 1;
            int target = 0 - nums[first];
            int second=first + 1;
            for( ;second<nums.length-1;second++){
                if (second > first + 1 && nums[second] == nums[second-1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }

                if (second == third) {
                    continue;
                }
                if (nums[second] + nums[third] == target){
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[first]);
                    list.add(nums[second]);
                    list.add(nums[third]);
                    res.add(list);
                }
            }
        }
        return res;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值