算法与数据结构实战(三)

3 篇文章 0 订阅
1 篇文章 0 订阅

算法与数据结构实战(三)

1.数学

题目: 两数之和

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。

你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

采用暴力破解 时间复杂度O(n^2)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        for(int i = 0; i < nums.length-1; i++){
            for(int j = i + 1; j < nums.length; j++){
                if(nums[j] == target - nums[i]){
                    return new int[]{i,j};
                }
            }
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}

使用HashMap 时间复杂度O(n)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            //注意点1 先判断后put()
            if(map.containsKey(target - nums[i])){
                return new int[]{i,map.get(target - nums[i])};
            }
            //注意点2 把nums[i]当键,把i当做值
            map.put(nums[i],i);
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}

2.数组&&链表

数组是内存中一串连续的内存地址,有一个内存管理器,可以随机访问任何一个数组下标的内存元素
Access: O(1)
Insert:平均O(n)
Delete:平均O(n)

由于数组不适合插入/删除,链表应运而生
Access: O(1)
Insert:平均O(n)
Delete:平均O(n)

题目: 反转链表(高频)

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL 
输出: 5->4->3->2->1->NULL

进阶:

你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

使用迭代

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;//第一次为1
        ListNode prev = null;//null
        while(cur != null){
            ListNode second = cur.next;//把当前节点的下一个节点存在second
            cur.next = prev;//
            prev = cur;//
            cur = second;//把second给当前节点
        }
        return prev;
    }
}

使用递归

class Solution {
    public ListNode reverseList(ListNode head) {
        //处理最小输入的情况,即空链表和单节点链表
        if (head == null || head.next == null) {
            return head;
        } 
        ListNode second = head.next; 
        ListNode reverseHead = reverseList(second); 
        second.next = head; 
        head.next = null; 
        return reverseHead; 
    }
}

题目:两两交换链表中的节点

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode h = new ListNode(-1);
        h.next = head;
        ListNode pre = h;
        while (pre.next != null && pre.next.next != null){
            ListNode node1 = pre.next;
            ListNode node2 = node1.next;
            ListNode lat = node2.next;
            
            pre.next = node2;        
            node2.next = node1;
            node1.next = lat;
            
            pre = node1;
         }
         return h.next;
    }

}

题目:环形链表(高频)

给定一个链表,判断链表中是否有环。

进阶:

你能否不使用额外空间解决此题?
思路:1、暴力破解,定时0.5秒,1秒 判断节点最后是否为null
2、用set数据结构存储节点,像狗一样留下气味,判断节点有没有重复,有重复则有环 O(n)时间复杂度
3、快慢指针,龟兔赛跑

龟兔赛跑

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast !=null && fast.next !=null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }
}

3.堆栈&&队列

有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。
示例 1:

输入: "()"
输出: true

示例 2:

输入: "()[]{}"
输出: true

示例 3:

输入: "(]"
输出: false

示例 4:

输入: "([)]"
输出: false

示例 5:

输入: "{[]}"
输出: true

思路1、左括号(包含大中小)–> push()
2、右括号(包含大中小)–> 先peek(),再pop()
3、判断栈isEmpty()

class Solution {
    private Map<Character,Character> map = new HashMap<>();
    public Solution(){
        map.put(')','(');
        map.put(']','[');
        map.put('}','{');
    }
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<Character>();
        //将字符串 拆分成多个字符
        for(int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            if(map.containsKey(c)){
                //字符是 右类型的括号 如果这是的栈里为空,就是不合法
                if(stack.isEmpty()){
                    return false;
                }
                //把栈的顶部元素拿出来,把它与 现在的右类型括号进行对比
                char topElement= stack.peek();
                if(map.get(c) == topElement){
                    stack.pop();
                    continue;
                }
                return false;
            }else{
                //字符是 左类型的括号 压入栈
                stack.push(c);
            }
            
        }
        return stack.isEmpty();
    }
    
}

用栈实现队列

class MyQueue {
    private Stack<Integer> stack1;
    private Stack<Integer> stack2;
    /** Initialize your data structure here. */
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    
    /** Push element x to the back of queue. */
    public void push(int x) {
        stack1.push(x);       
    }

    /** Removes the element from in front of queue and returns that element. */
    public int pop() {
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
            stack2.push(stack1.pop());
        }
    }
    return stack2.pop();
}

用队列实现栈

class MyStack {
    private LinkedList<Integer> q1;
    private LinkedList<Integer> q2;
    /** Initialize your data structure here. */
    public MyStack() {
        q1 = new LinkedList<>();
        q2 = new LinkedList<>();
    }
    /** Push element x onto stack. */
    public void push(int x) {
        q1.add(x);
    }
    
    /** Removes the element on top of the stack and returns that element. */
     public int pop() {
        if( q1.size() == 1){
            return q1.poll();
        }else{
            while(q1.size() > 1){
                q2.add(q1.poll());
            }
            while(q2.size() > 0){
                q1.add(q2.poll());
            }
            return q1.poll();
        }
    }   
    /** Get the top element. */
    public int top() {
        if(q1.isEmpty()){
           while(!q2.isEmpty()){
               q1.add(q2.poll());
           }
        }
        if(q1.size() == 1){
            return q1.peek();
        }else{
            while(q1.size() > 1){
                q2.add(q1.poll());
            } 
            // while(q2.size() > 0){
            //     q1.add(q2.poll());
            // }
            return q1.peek();
        }       
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return q1.isEmpty() && q2.isEmpty();
    }
}

数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

思路: 1、筛选出前k大的数字,每一次都对它们进行排序,时间复杂度是O(N*klogk)
2、使用优先队列(小顶堆,即小顶堆的顶部永远是最小的),保证小顶堆的size = k ,时间复杂度最好O(1),最差O(log2K),优于Klogk,快了10倍。

class Solution {

    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> q = new PriorityQueue<>(k);
        for(int i = 0; i < nums.length; i++){
            if(q.size() < k){
                q.offer(nums[i]);
            }else if(q.peek() < nums[i]){
                q.poll();
                q.offer(nums[i]);
            }  
        }
        return q.peek();
        
    }
}

滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。

返回滑动窗口最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

注意:

你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。

进阶:

你能在线性时间复杂度内解决此题吗?

思路:
1、使用优先队列最大堆维护k个数据,每次维护都得排序 时间复杂度O(N*logk)
2、使用队列(双端队列),维护k个数据,时间复杂度O(N)

class Tmp{
    public Tmp() {
    }
 
    public Tmp(Integer num,Integer pos) {
        this.pos = pos;
        this.num = num;
    }
 
    public Integer pos;
    public Integer num;
 
    public Integer getNum() {
        return num;
    }
 
    public void setNum(Integer num) {
        this.num = num;
    }
 
    public Integer getPos() {
        return pos;
    }
 
    public void setPos(Integer pos) {
        this.pos = pos;
    }
 
}

class Solution {
    public ArrayList<Integer> maxSlidingWindow(int[] nums, int k) {
       ArrayList<Integer> arr = new ArrayList<>();
       PriorityQueue<Tmp> q = new PriorityQueue(k, new Comparator<Tmp>() {
            @Override
            public int compare(Tmp o1, Tmp o2) {
                return o2.num - o1.num;
            }
        });
       for(int i = 0; i < nums.length; i++){
           if(q.size() < k){
               q.offer(new Tmp(nums[i],i)); 
           }else{
               q.offer(new Tmp(nums[i],i)); 
               Tmp p = q.peek();
               while(p.getPos() < i-(k-1)){
                   q.poll();
                   p = q.peek();
               }
               arr.add(p.getNum());
           }
       }
       return arr;
    }
}

有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。

示例 1:

输入: s = “anagram”, t = “nagaram”
输出: true

示例 2:

输入: s = “rat”, t = “car”
输出: false

说明:
你可以假设字符串只包含小写字母。

进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

思路:

  1. 使用排序,按字母字典顺序排序,时间复杂度(Klogk),比较排完顺序后的值

  2. 使用Map,统计出每个字符出现的次数{ letter,count}, for循环复杂度N Map每次插入O(1),总体复杂度O(N)

    class Solution {
        public boolean isAnagram(String s, String t) {
            
            if(s.length() != t.length()){
                return false;
            }
    		
        int[] sArray = new int[26];
        int[] tArray = new int[26];
    
        for (int i = 0; i < s.length(); i++) {
            //charAt(i) 就是在第i个位置的字符
            sArray[s.charAt(i)-97] ++;
            tArray[t.charAt(i)-97] ++;
        }
    
        for (int i = 0; i < 26; i++) 
            if (sArray[i]!=tArray[i]){
                return false;
            }
            return true;        
        }
    }
    

树&&二叉树&&二叉搜索树

验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出: true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
        根节点的值为 5 ,但是其右子节点值为 4 。
class Solution {
    public boolean isValidBST(TreeNode root) {
        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }
    
    public boolean isValidBST(TreeNode root, long minVal, long maxVal) {
        if (root == null) return true;
        if (root.val >= maxVal || root.val <= minVal) return false;
        return isValidBST(root.left, minVal, root.val) && isValidBST(root.right, root.val, maxVal);
    }
}

二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。

递归法

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q){
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        return left == null ? right : right == null ? left : root ;
    }
}

循环法 二叉搜索树的最近公共祖先

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if( p.val < root.val && root.val > q.val){
            return lowestCommonAncestor(root.left,p,q);
        }
        if( p.val > root.val && root.val < q.val){
            return lowestCommonAncestor(root.right,p,q);
        }
        return root;
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值