4.11·算法+数据结构

文章介绍了滑动窗口在LeetCode76和209题中的应用,用于寻找包含特定字符的最小子串和和大于等于目标值的最小子数组。同时,文章还讲解了链表、栈和队列的基础操作,包括插入、删除、查找等,并展示了如何使用栈解决括号匹配问题,以及队列在开车旅行问题中的应用。此外,还提到了哈希表在存储和查找数据中的作用。
摘要由CSDN通过智能技术生成

滑动窗口

Leetcode 76

题目描述:给你一个字符串 S、一个字符串 T 。请在字符串 S 里面找出:包含 T 所有字符的最小子串。

class Solution {
    public String minWindow(String s, String t) {
        int[] countT = new int[128];
        for (char c : t.toCharArray()) {  // 统计t中各个字符出现频率
            countT[c]++;
        }

        int left = 0, right = 0, minLength = Integer.MAX_VALUE, start = 0, cnt = t.length();
        while (right < s.length()) {  // 右指针不断扩张直到匹配成功
            char currRight = s.charAt(right);
            if (countT[currRight] > 0) {  // 当前字符在t中出现过
                cnt--;
            }
            countT[currRight]--;
            right++;

            while (cnt == 0) {  // 左指针开始缩小范围直到不能再缩
                if (right - left < minLength) {
                    minLength = right - left;
                    start = left;
                }
                char currLeft = s.charAt(left);
                countT[currLeft]++;
                if (countT[currLeft] > 0) {
                    cnt++;
                }
                left++;
            }
        }
        return minLength == Integer.MAX_VALUE ? "" : s.substring(start, start + minLength);
    }
}
  • 先统计t中各个字符出现的频率,并存储在数组countT中
  • 右指针right开始从0位置向右扩张,每次移动将当前字符的出现次数减1
  • 判断当前字符是否在t中出现过,若出现过,则计数器cnt减1
  • 当cnt变为0时说明已经找到一个子串包含了t中所有字符,此时我们需要缩小范围以寻找最短子串,于是开始移动左指针left,每当恢复缺失的字符后,就检查当前子串长度是否缩短,如果是则更新minLength和start变量
  • 最终返回[start, start + minLength)所代表的子串

Leetcode 209(长度最小的子数组)

题目描述:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小子数组。如果不存在符合条件的子数组,返回 0。

public class test{
    public static void main(String[] args) {
        int target = 7 ;
        int[] num = {2,3,1,2,4,3};
        System.out.println(minSubArrayLen(target, num));
    }

    public static int minSubArrayLen(int s, int[] nums) {
        int n = nums.length;
        if (n == 0) return 0;

        int i = 0;  // 滑动窗口左指针
        int j = 0;  // 滑动窗口右指针
        int sum = nums[0];  // 当前滑动窗口内元素的和
        int ans = Integer.MAX_VALUE;  // 记录子数组最小长度,初始化为 int 类型最大值

        while (true) {
            if (sum >= s) {  // 如果当前窗口元素之和满足条件,则尝试将左指针往右移动,并更新 result
                ans = Math.min(ans, j - i + 1);
                sum -= nums[i];
                i++;
            } else {
                j++;  // 否则将右指针继续向右移动
                if (j == n) break;
                sum += nums[j];
            }
        }

        return ans == Integer.MAX_VALUE ? 0 : ans;  // 如果没有满足条件的子数组,则返回 0
    }
}

① 首先我们定义两个指针 left 和 right,分别表示滑动窗口的左右边界;

② 然后通过移动右指针 right,来扩张滑动窗口,使得当前的滑动窗口内元素之和大于等于目标值 s。如果当前滑动窗口内元素之和不满足要求,则继续向右扩张窗口;

③ 当窗口内元素之和达到了要求之后,尝试移动左指针 left 缩小窗口并且记录当前窗口大小,并更新最小值;

④ 反复进行步骤②和③,直到右边界到达数组末尾。

⑤sum 表示当前滑动窗口内元素之和。每次通过平移右指针来扩展滑动窗口,在此过程中使用 while 循环不断进行 i 和 j 的变动操作,以及更新结果的最小值。

数据结构​​​​​​​

链表基础

public class LinkedList {
    
    // 声明一个内部类作为节点模板,包括存储数据和指向下一个节点的指针
    private static class Node {
        int data;
        Node next;
        
        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    }
    
    private Node head; // 链表头节点
    
    // 添加新节点到链表
    public void addNode(int data) {
        Node newNode = new Node(data); 
        
        if (head == null) { // 若链表为空,则新节点为首节点(头节点)
            head = newNode;
            return;
        }
        
        Node curr = head;
        while (curr.next != null) { // 找到最后一个节点
            curr = curr.next;
        }
        curr.next = newNode; // 将新节点连接到最后一个节点的next
    }
    
    // 在指定位置插入新节点
    public void insertNode(int data, int index) {
        if (index < 0) {
            System.out.println("Index cannot be negative.");
            return;
        }
        
        Node newNode = new Node(data);
        
        if (index == 0) { // 如果要在头节点前插入,则直接更新头节点引用
            newNode.next = head;
            head = newNode;
            return;
        }
        
        Node curr = head;
        for (int i = 0; i < index - 1; i++) { // 找到索引位置的前一个节点
            if (curr == null) { // 判断链表是否为null
                System.out.println("Index out of range.");
                return;
            }
            curr = curr.next;
        }
        
        newNode.next = curr.next; // 将新节点连接到索引位置的节点之后
        curr.next = newNode;
    }
    
    // 根据索引获取节点值
    public int getNode(int index) {
        if (index < 0) {
            System.out.println("Index cannot be negative.");
            return -1;
        }
        
        Node curr = head;
        for (int i = 0; i < index; i++) { // 找到指定索引位置的节点
            if (curr == null) { // 判断链表是否为null
                System.out.println("Index out of range.");
                return -1;
            }
            curr = curr.next;
        }
        
        if (curr == null) { // 如果找不到索引位置的节点,则返回-1
            return -1;
        }
        return curr.data;
    }
    
    // 删除指定索引位置的节点
    public void deleteNode(int index) {
        if (index < 0) {
            System.out.println("Index cannot be negative.");
            return;
        }
        
        if (index == 0) { // 删除头节点时,直接将head指向下一个节点
            head = head.next;
            return;
        }
        
        Node curr = head;
        Node prev = null;
        for (int i = 0; i < index; i++) { // 找到要删除的节点及其前一个节点
            if (curr == null) { // 判断链表是否为null
                System.out.println("Index out of range.");
                return;
            }
            prev = curr;
            curr = curr.next;
        }
        
        if (curr == null) { // 如果找不到索引位置的节点,则返回
            return;
        }
        prev.next = curr.next; // 将要删除的节点之前的节点与之后的节点连接起来,相当于删除了该节点。
    }
    
    // 获取链表节点数目
    public int countNode() {
        int count = 0;
        Node curr = head;
        while (curr != null) {
            count++;
            curr = curr.next;
        }
        return count;
    }
    
    // 打印当前链表
    public void printLinkedList() {
        Node curr = head;
        while (curr != null) {
            System.out.print(curr.data + " ");
            curr = curr.next;
        }
        System.out.println();
    }
}

在这里,我们采用内部类Node表示链表中的节点,其中包括链表数据以及指向下一个节点的指针。在主类中,实现了一些基本操作,例如添加新节点、从指定位置插入新节点、根据索引获取节点值、删除指定索引位置的节点、计算节点数量以及打印整个链表。各方法已经有详细注释,帮助你更好地理解每个步骤的作用。

栈与队列

栈的基本操作

public class Stack {
    
    private int[] arr;
    private int size; // 栈中元素数量
    
    public Stack(int capacity) {
        arr = new int[capacity];
        size = 0;
    }
    
    // 将元素压入栈顶
    public void push(int item) {
        if (size == arr.length) { // 判断是否已满
            System.out.println("Stack is full.");
            return;
        }
        arr[size] = item;
        size++;
    }
    
    // 返回并移除栈顶元素
    public int pop() {
        if (size == 0) { // 判断是否为空
            System.out.println("Stack is empty.");
            return -1;
        }
        size--;
        return arr[size];
    }
    
    // 返回栈顶元素
    public int peek() {
        if (size == 0) { // 判断是否为空
            System.out.println("Stack is empty.");
            return -1;
        }
        return arr[size-1];
    }
    
    // 判断栈是否为空
    public boolean isEmpty() {
        return size == 0;
    }
    
    // 获取栈中元素数量
    public int size() {
        return size;
    }
}

队列的基本操作 

public class Queue {
    
    private int[] arr;
    private int front; // 队首元素索引
    private int rear; // 队尾元素索引
    private int capacity; // 最大容量
    
    public Queue(int capacity) {
        this.arr = new int[capacity];
        this.front = 0;
        this.rear = -1;
        this.capacity = capacity;
    }
    
    // 将元素插入队尾
    public void enqueue(int item) {
        if (rear == capacity-1) { // 判断队列是否已满
            System.out.println("Queue is full.");
            return;
        }
        rear++;
        arr[rear] = item;
    }
    
    // 返回并删除队首元素
    public int dequeue() {
        if (isEmpty()) { // 判断队列是否为空
            System.out.println("Queue is empty.");
            return -1;
        }
        int item = arr[front];
        front++;
        return item;
    }
    
    // 返回队首元素
    public int peek() {
        if (isEmpty()) { // 判断队列是否为空
            System.out.println("Queue is empty.");
            return -1;
        }
        return arr[front];
    }
    
    // 判断队列是否为空
    public boolean isEmpty() {
        return front > rear;
    }
    
    // 获取队列大小
    public int size() {
        return rear - front + 1;
    }
}

循环队列

public class CircularQueue {
    
    private int[] arr; // 数组用于存储元素
    private int front; // 队首元素索引
    private int rear; // 队尾元素索引
    private int size; // 当前队列元素数量
    private int capacity; // 最大容量
    
    public CircularQueue(int capacity) {
        this.capacity = capacity;
        this.arr = new int[capacity];
        this.front = 0;
        this.rear = -1;
        this.size = 0;
    }
    
    // 入队操作
    public void enqueue(int item) {
        if (isFull()) { // 判断队列是否已满
            System.out.println("Queue is full.");
            return;
        }
        rear = (rear + 1) % capacity; // 计算新的队尾指针位置
        arr[rear] = item; // 将新元素插入队尾
        size++; // 队列元素数量加1
    }
    
    // 出队操作
    public int dequeue() {
        if (isEmpty()) { // 判断队列是否为空
            System.out.println("Queue is empty.");
            return -1;
        }
        int item = arr[front]; // 获取当前队首元素值
        front = (front + 1) % capacity; // 计算新的队首指针位置
        size--; // 队列元素数量减1
        return item;
    }
    
    // 返回队首元素
    public int peek() {
        if (isEmpty()) { // 判断队列是否为空
            System.out.println("Queue is empty.");
            return -1;
        }
        return arr[front];
    }
    
    // 判断队列是否已满
    public boolean isFull() {
        return size == capacity;
    }
    
    // 判断队列是否为空
    public boolean isEmpty() {
        return size == 0;
    }
    
}

入队操作

采用element = (element + 1) % array.length的方式计算新的队尾指针位置。

出队操作

dequeue方法会返回当前队首元素的值并将它从队列中删除。采用front = (front + 1) % array.length的方式计算队首所在位置,即先把对头移位以让队列可以在任意时刻有序做出弹出操作,使能 "空出" 队头的位置重新入队。

其他属性和方法

该实现还包括isFull、isEmpty和peek方法。 isFull和isEmpty分别检查队列是否为满和是否为空,而peek在返回队列顶部元素时不执行删除。

栈的应用

括号的匹配问题

  1. 创建一个栈
  2. 遍历字符串中的每个字符
  3. 如果是左括号,则推入栈中
  4. 如果是右括号,则从栈中弹出一个元素,并将其与右括号进行匹配。如果匹配失败,则返回false
  5. 最后检查栈是否为空。如果不为空,则表示有未匹配的左括号,返回false
public class BracketMatching {
    public static boolean isMatch(String s) {
        Stack<Character> stack = new Stack<>();
        for (char c : s.toCharArray()) {
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);
            } else {
                if (stack.isEmpty()) {
                    return false;
                }
                char match = stack.pop();
                if (c == ')' && match != '(' || c == ']' && match != '[' || c == '}' && match != '{') {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }
}

队列的应用

开车旅行问题

问题描述:假设有一队N(N>1)台车要从A点到B点,第i辆车油量为gas[i],从A点到B点需要cost[i]的油量。假设每辆车加满油可以走无限远,也就是说,从A点出发,最开始时油箱是空的。如果从A点出发,默认车辆按照队列中的顺序行驶,问从哪台车开始出发,可以使每台车都能够到达B点?

思路:

  1. 如果所有汽车的油量减去耗油量之和小于零,则不存在满足题意的结果。
  2. 否则,就用一个整数变量start记录当前扫描的起始位置,并且定义另外一个变量sum来记录油箱中剩余的油量。
  3. 从第一辆车开始扫描所有的车辆,并每次将sum减去cost[i],再加上gas[i],直到遇到sum小于零的情况。
  4. 如果当前i不满足条件,那么i之前所有的车都不能作为起始点,因为它们的油量减去耗油量之和肯定也小于零。
public class CarTrip {
    public static int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length;
        int sum = 0; // 记录油量与消耗差值之和
        int tank = 0; // 记录油量,表示起点到当前车站还能剩余的油量
        int start = 0; // 记录起始位置
        for (int i = 0; i < n; i++) {
            sum += gas[i] - cost[i];
            tank += gas[i] - cost[i];
            if (tank < 0) { // 如果油量不够则重新选择起点,并将可用的油量重置为0
                start = i + 1;
                tank = 0;
            }
        }
        // 如果油量差总和小于零,则返回-1表示无法从任意地点出发到达终点
        return sum < 0 ? -1 : start;
    }
}

哈希表​​​​​​​

import java.util.HashMap;

public class test{
    public static void main(String[] args) {
        HashMap<String, Integer> phoneBook = new HashMap<>();
        phoneBook.put("wlc", 123);
        phoneBook.put("A宝", 456);
        phoneBook.put("wlc", 111);
        System.out.println(phoneBook.size());

        boolean containKey = phoneBook.containsKey("A宝");
        boolean containValue = phoneBook.containsValue(451);
        System.out.println(containKey);
        System.out.println(containValue);

        int APhoneNum = phoneBook.get("A宝");
        System.out.println(APhoneNum);

        System.out.println(phoneBook);
        phoneBook.remove("wlc");
        System.out.println(phoneBook);


    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值