滑动窗口
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在返回队列顶部元素时不执行删除。
栈的应用
括号的匹配问题
- 创建一个栈
- 遍历字符串中的每个字符
- 如果是左括号,则推入栈中
- 如果是右括号,则从栈中弹出一个元素,并将其与右括号进行匹配。如果匹配失败,则返回false
- 最后检查栈是否为空。如果不为空,则表示有未匹配的左括号,返回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点?
思路:
- 如果所有汽车的油量减去耗油量之和小于零,则不存在满足题意的结果。
- 否则,就用一个整数变量start记录当前扫描的起始位置,并且定义另外一个变量sum来记录油箱中剩余的油量。
- 从第一辆车开始扫描所有的车辆,并每次将sum减去cost[i],再加上gas[i],直到遇到sum小于零的情况。
- 如果当前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);
}
}