227.基本计算器II
思路:使用栈的思想,然后 默认第一个数字之前的符号是+
之后开始遍历得到最长的数字(不是一位数字!),如果遇到了符号 (非空格)那么参考之前保存的数字以及之前的符号 进行操作(这个思想很重要)
符号 + 入栈
符号- 当前数字的负数入栈
符号 * 栈顶出栈 乘以 当前的数字后入栈
符号 / 栈顶出栈 除以 当前的数字之后出栈
class Solution {
public int calculate(String s) {
char[] arr = s.toCharArray();
Deque<Integer> stack = new LinkedList<>();
char preSign = '+' ; //用来保存数字之前的标记 默认第一个数字之前的标记是+
int num = 0;
for(int i = 0; i< arr.length;i++) {
//如果是数字那么就带着当前的数字进入 但是我需要做到的是连续的数字可以进行运算的!
//如果当前的是数字 -- 要处理的是连续最长的数字
if (Character.isDigit(arr[i])) {
num = num * 10 + arr[i] - '0';
continue;
}
//但是有一个问题就是如果最后一个是数字遇不到下一个符号之前的东西都拜拜了
if (!Character.isDigit(arr[i]) && arr[i] != ' ') {
//遇到了一个新的符号 把之前的数字处理一下
switch (preSign) {
case '+':
stack.push(num);
break;
case '-':
stack.push(-num);
break;
case '*':
stack.push(stack.pop() * num);
break;
case '/':
stack.push(stack.pop() / num);
break;
default:
break;
}
//然后更改符号 再次记录
preSign = arr[i];
num = 0;
}
}
//对最后的数字进行处理
switch (preSign){
case '+':stack.push(num); break;
case '-':stack.push(- num); break;
case '*':stack.push(stack.pop() * num);break;
case '/':stack.push(stack.pop() /num);break;
default:break;
}
int res = 0;
while (stack.size() != 0){
res += stack.remove();
}
//最后返回的是栈顶的元素
return res;
}
}
上面的代码有些繁琐主要是因为最后一个数字之后没有符号了 那么之前的数字就没有办法处理了所以在判断条件那里加上一句如果当前已经遍历到最后一个元素也同样进行操作即可
class Solution {
public int calculate(String s) {
char[] arr = s.toCharArray();
Deque<Integer> stack = new LinkedList<>();
char preSign = '+' ; //用来保存数字之前的标记 默认第一个数字之前的标记是+
int num = 0;
for(int i = 0; i< arr.length;i++) {
//如果是数字那么就带着当前的数字进入 但是我需要做到的是连续的数字可以进行运算的!
//如果当前的是数字 -- 要处理的是连续最长的数字
if (Character.isDigit(arr[i])) {
num = num * 10 + arr[i] - '0';
}
//但是有一个问题就是如果最后一个是数字遇不到下一个符号之前的东西都拜拜了
if (!Character.isDigit(arr[i]) && arr[i] != ' ' || i == arr.length-1) {
//遇到了一个新的符号 把之前的数字处理一下
switch (preSign) {
case '+': stack.push(num);break;
case '-':stack.push(-num);break;
case '*':stack.push(stack.pop() * num);break;
case '/': stack.push(stack.pop() / num); break;
default: break;
}
//然后更改符号 再次记录
preSign = arr[i];
num = 0;
}
}
int res = 0;
while (stack.size() != 0){
res += stack.remove();
}
//最后返回的是栈顶的元素
return res;
}
}
3. 无重复的最长字串
思路:滑动窗口
left 指向最左端 right 依次往后++,使用set 存储已经走过的字符。
如果set 里面没有 right 一直往后走 count++ 更新max
set里面有 左指针移动 left – 同时移除 set中的left指向的元素
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s == null || s.length() == 0) return 0;
if(s.length() == 1) return 1;
HashSet<Character> set = new HashSet<>();
int left = 0;
int count = 1;
int max = 1;
set.add(s.charAt(left));
int right = 1;
while(right < s.length()){
if(!set.contains(s.charAt(right))){
set.add(s.charAt(right));
count++;
right++;
max = Math.max(max,count);
}else{
set.remove(s.charAt(left));
left++;
count --;
}
}
return max;
}
}
402.移掉k位数字
使用贪心的思路+构建一个单调栈
栈的特性:单调不降(也就是紧挨的元素要么是单调递增的 要么是相同的,不会出现 下一个入栈的元素比栈顶元素小的情况)
删除一个数字的策略:
基于此,我们可以每次对整个数字序列执行一次这个策略;删去一个字符后,剩下的 n-1 长度的数字序列就形成了新的子问题,可以继续使用同样的策略,直至删除 k 次。
有一些特殊的测试用例需要注意一些
“10” 2 - > “0”
“10” 1 - > “0”
“0002” 1 - > “000” - > “0”
字符串的题说白了就是考虑细节!!
思路一 : 暴力循环 使得数字系列满足单调不降
此种方法采用 o(nk) 的时间复杂度,相对来说不太好
class Solution {
public String removeKdigits(String num, int k) {
if(num == null) return null;
if(num.length() == 0 || num.length() <= k) return "0";
StringBuilder sb = new StringBuilder(num);
while(k > 0){
//从左向右遍历 找到使得数字开始减小的删除
sb = remove(sb);
//如果操作之后和之前没有变化就删除最尾部的元素
k--;
}
//如果是以0来头 那么删除直到不以0开头结束
while (sb.length() != 0 && sb.charAt(0) == '0'){
sb.deleteCharAt(0);
}
//如果经过上述的操作之后,元素都删除光了那么就返回“0”即可
if(sb.toString().equals("")) return "0";
return sb.toString();
}
private static StringBuilder remove(StringBuilder sb) {
for(int i = 1; i< sb.length();i++){
if(sb.charAt(i-1) > sb.charAt(i)){
sb.deleteCharAt(i-1);
//找到一个就结束了当此的删除
break;
}else if(i == sb.length()-1) {
//表示并没有找到违背单调不降的元素
sb.deleteCharAt(i);
}
}
return sb;
}
}
思路二:维护一个单调不降的栈
因为最后还涉及到元素出栈需要颠倒顺序,所以使用双端队列
class Solution {
public String removeKdigits(String num, int k) {
if (num == null) return null;
if (num.length() == 0 || num.length() <= k) return "0";
//构建一个双端队列 而不是简单的栈这样出的时候比较方法
Deque<Character> deque = new LinkedList<>();
char[] arr = num.toCharArray();
int index =0 ;
//如果没有遍历完所有的元素 并且k > 0
while (index < arr.length && k > 0){
//如果进来是空的
if(deque.size() == 0 ) deque.addLast(arr[index++]);
else{
//说明不是空的 那么就可以进行比较了
if(arr[index] < deque.peekLast()){ //如果当前遍历到的元素比栈顶小 栈顶元素出栈
deque.removeLast();
k--;
}
//如果不大于直接入队
else deque.addLast(arr[index++]);
}
}
//说明此时的栈已经是一个单调不降的
while ( k > 0){
deque.removeLast();
k--;
}
//最后要把所有没有遍历到元素放进栈、
while (index < arr.length){
deque.addLast(arr[index++]);
}
StringBuilder sb = new StringBuilder();
while (deque.size() != 0 && deque.peekFirst() == '0') {
//去除所有的开头0
deque.removeFirst();
}
while (deque.size() != 0){
sb.append(deque.removeFirst());
}
//如果处理完0以后是空的
if(sb.toString().equals("")) return "0";
return sb.toString();
}
}
100.相同的树
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) return true;
if(p == null || q == null) return false;
if(p.val != q.val) return false;
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}
53.最大子序和
思路:动态规划
class Solution {
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
//最大的和
int max = nums[0];
//当前的和
dp[0] = nums[0];
for(int i = 1 ; i < nums.length;i++){
dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
max = Math.max(max,dp[i]);
}
return max;
}
}
优化
class Solution {
public int maxSubArray(int[] nums) {
int last = 0;
//最大的和
int max = nums[0];
//当前的和
for(int i = 0 ; i < nums.length;i++){
last = Math.max(last+nums[i],nums[i]);
max = Math.max(max,last);
}
return max;
}
}
1143. 最长公共子序列 (※※※)
这个题主要是给和上面最大子序和对比来做。
子序列可以是不连续的;子数组(子字符串)需要是连续的;
设计动态规划的套路:
单个字符串或者是数组使用动态规划的时候,可以把状态定义设置为dp[ i ] 定义为 nums[0:i] 要求的结果
当两个数组或者是两个字符串的时候,可以把动态规划定义为dp[ i ][ j ] 含义是 在 A[0:i] 与 B[0:j] 之间匹配得到的想要的结果。
关于状态转移的说明
如果当前遍历到的两个字符是相等的,那么状态转移 dp[i][j] = dp[i-1] [j-1] + 1 也就是当前的状态是可以由上一个状态进行延申+1的
如果当遍历到的两个字符不相等,那么当前的状态是必须要继承 dp[i] [j-1] 和 dp[i-1] [j] 的最大值的。
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int length1 = text1.length();
int length2 = text2.length();
int[][] dp = new int[length1+1][length2+1];
/**
* dp[i][j]
* 表示的意思的是第一个字符串 [0,i-1]
* 和第二个字符串 [0,j-1]的最长公共子序列
* 为什么要这么设计呢
* dp[i,j] 为什么不定义成 [0,i] [0,j]呢
* 方便dp[0][0] 表示的时候是两个字符串都是空的状态这样最长的公共子序列是0
* 当i = 0 || j == 0 的时候,对应的都是0
*/
for(int i =1; i<= length1;i++){
for(int j = 1; j <= length2;j++){
if(text1.charAt(i-1) == text2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+1;
}else {
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[length1][length2];
}
}
422.数组中重复的数据
思路一:数组排序然后暴力遍历
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> list = new ArrayList<>();
Arrays.sort(nums);
for(int i = 0; i < nums.length-1;i++){
if(nums[i] == nums[i+1]){
list.add(nums[i]);
i++;
}
}
return list;
}
}
思路二:原地哈希
题目提供了一个条件就是当前的数字范围是在 1- 数组的长度之间的。
这种做法找出现几次的都可以做到
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> list = new ArrayList<>();
int length = nums.length;
for(int i = 0;i< length;i++){
//这里 % length 是处理可能会数据越界的情况
// %运算的优先级高于 -
nums[(nums[i]-1) %length] += length;
}
for(int i = 0; i< length;i++){
if(nums[i] >= 2*length+1 && nums[i] <= 3*length){
list.add(i+1);
}
}
return list;
}
}
199.二叉树的右视图
其实主要是树的层序遍历,然后在遍历每一层的时候,判断当前遍历到是第几个即可。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
//还是进行树的层序遍历
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> res = new LinkedList<>();
if(root == null) return res;
queue.add(root);
while (queue.size() != 0){
int size = queue.size();
for(int i = 0;i < size;i++){
TreeNode temp = queue.remove();
if(i == size-1){
res.add(temp.val);
}
if(temp.left != null){
queue.add(temp.left);
}
if(temp.right != null){
queue.add(temp.right);
}
}
}
return res;
}
}
171.Excel表列序号
我愿称这道题的名字是:我是傻瓜!!!
如果字符串有n位 那么首先得出 n-1 可以算出来的数字和,然后加上第n位造成的数字和的累加。
第n位造成数字和的累加和第n位取值是由关系的 如果是A 那么产生新的需要累加的数字是:1* 26^(n-1)
如果是B 那么产生新的需要累加的数字是:2 * 26 ^(n-1)
“FXSHRXW” 中的每个字母对应的序号分别是:[6,24,19,8,18,24,23] (其中 A 到 Z 分别对应 1 到 26),则列名称对应的列序号为
23 * 26^0 + 24 * 26^1 + 18 * 26 ^2 +…+6 * 26^6
class Solution {
public int titleToNumber(String columnTitle) {
int num = 0;
int jinzhi = 1;
for(int i = columnTitle.length()-1;i >= 0; i--){
num += (columnTitle.charAt(i)-'A'+1) * (jinzhi);
jinzhi *= 26;
}
return num;
}
}
20.有效的括号
基本思想:遇到左括号进栈,遇到和栈顶匹配的右括号出栈,最后判断是否栈为空,为空则说明完全匹配。不为空返回false
如果遇到右括号不匹配栈顶 那么说明整体不匹配,返回 fasle 。
如果遇到了右括号,但是栈里面没有左括号,也返回false
class Solution {
public boolean isValid(String s) {
Deque<Character> stack = new LinkedList<>();
for(int i = 0; i<s.length();i++){
if (s.charAt(i) == '(' || s.charAt(i) == '[' || s.charAt(i) == '{') {
stack.push(s.charAt(i));
} else if (stack.size() != 0) {
Character ch = stack.peek();
Character cur = s.charAt(i);
if (cur == ')' && ch == '(') {
stack.pop();
}else if (cur == ']' && ch == '[') {
stack.pop();
}else if (cur == '}' && ch == '{') {
stack.pop();
}else{
return false;
}
} else {
return false;
}
}
return stack.size() == 0;
}
}