一、字符串中的单词反转(反转字符串/双指针去空格)
你的朋友喜欢从右往左读,给你一个正序的字符串。将它设置为反序的 并且单词之间或者开头有若干个空格(最少是1)
输入:
the sky is blue
输出
blue is sky the
大致思路:
1.先将整个字符串反转一下。这样单词的位置就正确了。
2.可以去空格/也可以将单词进行反转(这样单词的顺序就正确了)
3.将单词进行反转
反转字符串的代码:
public char[] reverse(char[] ch, int left, int right) {
while (left < right) {
char temp = ch[left];
ch[left] = ch[right];
ch[right] = temp;
left++;
right--;
}
return ch;
}
去除空格(使用双指针):(和删除元素类似)
1.当快指针所指向的位置为' '的时候,fast++
2.当快指针指向的不是空的时候,并且slow指向不是下标0的时候(第一个单词是不用加空格的),此时要先在单词前面加一个空格,然后使用while循环遍历,将整个单词复制。
代码:
int fast=0;int slow=0;
while(fast<ch.length){
//跳过空格
while(fast<ch.length&&ch[fast]==' ')fast++;
//复制单词
while(fast<ch.length&&ch[fast]!=' '){
if(slow!=0){
ch[slow++]=' ';
}
while(fast<ch.length&&ch[fast]!=' '){
ch[slow++]=ch[fast++];
}//这个循环结束那么外层的while循环也会结束的
}
}
ch=Arrays.copyOf(ch,slow);//要更新ch的长度
将每个单词都反转:
使用for循环,当遇到空格的时候就停下来,对前面的字符串进行反转。可以使用index计数器然后计算字符串的长度,使用reverse(ch,left,right)进行反转。
注意:
1.当第一个单词找到之后,新的left要更新至index+1,因为index这个位置是空格。
2.因为是找到空格 然后更新前面的单词。但是最后一个单词前面是没有空格的,所以要特殊考虑最后一种情况
int left=0;
for(int i=0;i<ch.length;i++){
if(i<ch.length&&ch[i]==' '){
//当遇到空格之后才会停止下来 进行翻转
reverse(ch,left,i-1);//翻转left->i-1的
left=i+1;//i是空格 直接跳到i+1
}else if(i==ch.length-1){
reverse(ch,left,i);
}
}
return new String(ch);
二、使用队列实现栈
双栈,一个输入,一个输出。因为栈的特点是先进后出,因此双栈就可以实现先进的先出。
将in栈中的数据都放到out栈中
private void in2out() {
// 将in栈中的元素都移到out栈中
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
队列的pop():
public int pop(){
if(outStack.isEmpty()){
in2out();
}
return outStack.pop();
}
队列中的peek():返回队列的首个元素
public int peek(){
int result=this.pop();
outStack.pull(result);//因为第一个元素已经弹出去了,所以要把它加回来
return result;
}
三、用队列实现栈
弹栈的时候pop(),因为栈具有先进后出的特点,队列具有先进先出的特点。
因此弹栈的时候,队列应该先将最后一个元素之前的元素都移到后面去。
疑惑的地方:在挨个获取元素往后移的时候,我只用移动n-1次。但是(n-1)次无法运行出正确的结果。n次才是正确的
public void push(int x) {
queue1.push(x);
}
public int pop() {
int size=queue1.size();
for (int i = 1; i <=size; i++) {
int number = queue1.peekFirst();// 挨个获取元素
System.out.println(i+":"+number);
queue1.pollFirst();
}
int res = queue1.pollFirst();//到了最后一个元素 弹出
return res;
}
四、有效的括号:
卡尔哥的思路非常好:当遇到( { [的时候,就将另一半的括号放到栈里面。如果遇到)}]就去和栈顶的元素做比较,如果无法匹配,return false;如果可以匹配,就将栈顶的元素弹出;在遍历完之后,如果遇到栈不为空,说明有括号没有消除(有多余的左括号或者右括号)。
为什么在遇到右半括号时,要将栈顶元素和ch[i]作比较?因为每一对括号都是相互对称的,先加进去的括号离得远,后进来的括号离得近,就要先比较。正好符合栈的先进后出的特点。
代码:
public boolean isValid(String s) {
Stack<Character> stack =new Stack<>();
char[] ch = s.toCharArray();
if ((ch.length) % 2 != 0)
return false;
for (int i = 0; i < ch.length; i++) {
if (ch[i] == '(')
stack.push(')');
else if (ch[i] == '[')
stack.push(']');
else if (ch[i] == '{')
stack.push('}');
else if (stack.isEmpty() || ch[i] != stack.peek()) {
return false;
} else {
stack.pop();
}
}
return stack.isEmpty();
}
五、删除字符串中的所有相邻重复项
我的想法:仍然利用栈的先进后出的特点。
1.当栈里面为空的话,直接push(char[i]);
2.1如果栈里面不为空,如果char[i]和栈顶元素相同的话,那么就pop掉栈顶元素。
2.2如果不相同,那么就push(char[i]);
最后栈里面剩下的元素就是结果。我这里使用reverse将它反转输出了。
public String removeDuplicates(String s) {
//将每次ch[i]和栈顶的元素相比较
//如果相同 就pop掉 如果不相同就push进去
Stack<Character> stack=new Stack<>();
char[] ch=s.toCharArray();
int size=ch.length;
for(int i=0;i<size;i++){
if(stack.isEmpty()){
stack.push(ch[i]);
}
else if(ch[i]!=stack.peek()){
stack.push(ch[i]);
}else if(ch[i]==stack.peek()){
stack.pop();
}
}
StringBuilder sb=new StringBuilder();
while(!stack.isEmpty()){
sb.append(stack.pop());
}
return reverse(sb.toString());
}
public String reverse(String str){
char[] ch=str.toCharArray();
int left=0;
int right=ch.length-1;
while(left<right){
char temp=ch[left];
ch[left]=ch[right];
ch[right]=temp;
left++;
right--;
}
return new String(ch);
}
六、逆波兰表达式求值:
将表达式按照二叉树的形式铺展下来,逆波兰表达式就是对二叉树进行后序遍历。而普通的写法是对二叉树的中序遍历。逆波兰表达式:12+34*+5- = 中序遍历(1+2) +3*4-5
规则:遇到数字添加到栈中,遇到表达式,从栈中弹出两个元素,运算之后加入到栈中。
逻辑很简单。
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList();
for (String s : tokens) {
if ("+".equals(s)) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
stack.push(stack.pop() + stack.pop()); // 注意 - 和/ 需要特殊处理
} else if ("-".equals(s)) {
stack.push(-stack.pop() + stack.pop());
} else if ("*".equals(s)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(s)) {
int temp1 = stack.pop();
int temp2 = stack.pop();
stack.push(temp2 / temp1);
} else {
stack.push(Integer.valueOf(s));
}
}
return stack.pop();
}
七、滑动窗口最大值(暴力法/单调队列)
暴力法的话,会有超时的问题。
暴力代码:
public int[] maxSlidingWindow(int[] nums, int k) {
int size=nums.length-k;
int max[]=new int[size+1];
for(int i=0;i<=size;i++){
int maxs=findMax(nums,i,i+k-1);
max[i]=maxs;
}
return max;
}
public int findMax(int []nums,int left,int right){
int max=nums[left];
for(int i=left;i<=right;i++){
if(nums[i]>max)max=nums[i];
}
return max;
}
单调队列:
如果它不是最大的,它不可能在最前边,因为最大的会被它消除。如果它在最前面,那么它一定是最大的。
自定义单调队列:
poll弹出:如果==val?弹出????
add添加:添加一个值,比前面谁大,谁就出去。removeLast();
peek取值:每次取顶点的值
代码:
先add四个元素,添加之后,最大的会在最前面。然后从k开始,从下标为0开始弹出,因为已经装满四个了,所以先弹出,再添加,再返回顶点。
class MyQueue {
// 自定义单调队列中包含poll弹出、add添加、peek查看顶点
Deque<Integer> deque = new LinkedList<>();
// poll弹出
void poll(int val) {
if (!deque.isEmpty() && deque.peek() == val) {
deque.poll();
}
}
// add添加
void add(int val) {
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();// 如果添加了一个元素 比前面的元素大 比谁大谁就被移出去
}
deque.add(val);
}
// 查看顶点(最大值)
int peek() {
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 首先剪枝操作
if (nums.length == 1)
return nums;
// 创建存放结果的数组
int size = nums.length - k + 1;
int[] result = new int[size];
// 然后创建自定义队列
MyQueue myqueue = new MyQueue();
for (int i = 0; i < k; i++) {
myqueue.add(nums[i]);
}
int num = 0;
result[num++] = myqueue.peek();
// 从下面这个循环就要开始移除了 因为已经装入了k个元素
for (int i = k; i < nums.length; i++) {
myqueue.poll(nums[i - k]);// i=0开始移除
myqueue.add(nums[i]);// 如果不大就放后面 如果大 前面的移除
result[num++] = myqueue.peek();
}
return result;
}
}
八、前K个高频元素(大根堆/小根堆)
大根堆和小根堆都满足完全二叉树的性质,父节点的下标为i,两个子节点的下标为2i+1,2i+2
大根堆:父节点比子节点大
小根堆:父节点比子节点小,最小的在上面。依次pop(),就是从小到大堆排序
上滤:根节点向上调整的操作。当处于上面的结点破坏了堆序性,就要进行上滤操作。维护堆的性质
下滤:根节点向下调整的操作。
比如在大根堆中,8作为6的子节点,却比6还要大。因此8要进行上滤,维护堆的性质
自上而下建堆法 (上滤)
自下而上建堆法(按照前后顺序先放到堆里面,然后根据下滤操作,将下面的结点往上移)
大根堆进行堆排序的时候,pop栈顶元素,然后将最后一个元素放到栈顶进行下滤操作,操作之后找到最大值然后再次pop,循环操作。
代码:
首先将各个数字出现的次数放到map集合中保存。然后将(num,cnt)插入到pq中。
pq:优先级队列,在创建pq的时候会定义一个比较规则作为参数。
public int[] topKFrequent(int[] nums, int k) {
//将各个数字出现的次数 放到一个map集合中 key:数组值 value:频率
Map<Integer,Integer> map=new HashMap<>();
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt是出现的次数
//优先级队列pq 参数里面是比较的规则 加进去就直接排好了
PriorityQueue<int[]> pq=new PriorityQueue<>((pair1,pair2)->pair2[1]-pair1[1]);
//将map集合中的数据放在pq中
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
int[] ans=new int[k];//大小为k的数组
for(int i=0;i<k;i++){
ans[i]=pq.poll()[0];
}
return ans;
}