Leetcode-1-- 数组\字符串\排序\栈队列

这里写目录标题

  • 排序\查找
    • 归并排序
    • 快速排序
    • Top K
  • 数组
    • 283 移动零 && 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
    • 剑指offer003 数组中重复的数字
    • 剑指 Offer 56 - I. 数组中数字出现的次数
    • 剑指 Offer 04. 二维数组中的查找
    • 顺子
    • 山型数组
    • 跳跃游戏
    • 二维无序数组找第一个不存在自然数
    • 541最短无序连续子数组
    • 两数之和
    • 三数之和
    • 最接近的三数和
    • 41 缺失的最小正数 ⭐️
    • 287 寻找重复数
    • 442 数组中重复的数据
    • 剑指 正整数数组组成的最大的数()
    • 31 下一个排列
    • 剑指66 构建乘积数组
    • 73矩阵置0(标记的两种方法)
    • 128 最长连续序列
    • 238 除自身以外数组的乘积
  • 整数
    • 9 回文数
    • 7 整数反转⭐️
    • 13 罗马数转整数
    • 69 x的平方根
    • 279. 完全平方数
    • 461 汉明距离
    • 470. 用 Rand7() 实现 Rand10()⭐️
    • 快速幂⭐️
    • 丑数
    • 质因数分解
  • 字符串
    • 双指针
      • 28. 实现 strStr()⭐️
      • 344. 反转字符串 541 反转字符串II ⭐️
      • 49. 字母异位词分组
      • 3 无重复字符的最长子串
      • 30
      • 76
      • 验证ip地址
      • 438. 找到字符串中所有字母异位词 ⭐️
    • 空格处理
      • 面试题05. 替换空格⭐️
      • 58. 最后一个单词的长度
      • 8. 字符串转换整数 (atoi) ⭐️
      • 连接所有空格
    • 递归&DP
      • 139. 单词拆分⭐️
    • 其他
      • 剑指offer67 把字符串转换成整数
      • 43 字符串乘法
      • 14. 最长公共前缀
      • 394. 字符串解码
      • 面试题 01.06. 字符串压缩
      • 844. 比较含退格的字符串
      • 3 无重复字符的最长子串
      • 30. 串联所有单词的子串
      • 76. 最小覆盖子串⭐️
      • 424. 替换后的最长重复字符
      • 647. 回文子串 ⭐️
      • 5 最长回文子串
      • 最长回文串
      • 1143最长公共子序列 (LCS) ⭐️
      • 300最长上升子序列 (LIS) ⭐️
      • 516 最长回文子序列
      • 最短包含子串
    • 括号匹配套题
      • 22. 括号生成
      • 32 最长有效括号
  • 栈、队列
    • 两个栈实现一个队列
    • 两个队列实现一个栈
    • 单调栈
      • 84 柱状图的最大矩形

排序\查找

归并排序

链表,主要是递归下去的过程;
无论是链表还是数组,都是 每次 把 当前数组/链表 分成两部分。。

class Solution {
    // nlogn 的排序-> 归并
    //归并一般是对两个有序数组或链表而言,那么怎么对一个序列用?
    //结合递归,递归到区间只有一个元素的时候,单个有序的元素肯定有序,递归返回的过程中进行归并,就是有序的序列两两归并了
    //数组 递归 到最后是 L==R,
    // 链表递归呢? 应该在递归找中点的过程中,随时让 中点的下一个节点为空,这样递归返回的条件就是 head==null ||head.next== null
    public ListNode sortList(ListNode head) {
        if(head==null||head.next==null) return head;
        ListNode mid = midNode(head);
        ListNode midNext = mid.next;
        mid.next = null;//这样其实是把链表节点拆成了一个一个单个的点
        ListNode l = sortList(head);
        ListNode r = sortList(midNext);
        return merge(l, r);
    }
    public ListNode merge(ListNode h1,ListNode h2){
        ListNode newHead = new ListNode(0);
        ListNode cur = newHead;
        while(h1!=null && h2 != null){
           if(h1.val < h2.val){
              cur.next = h1;
              h1 = h1.next;
           }else{
               cur.next = h2;
               h2 = h2.next;
           }
          cur = cur.next;
        }
        if(h2 !=null){
            h1 = h2;
        }
        while(h1 !=null){
            cur.next = h1;
            h1 = h1.next;
            cur = cur.next;
        }
        return newHead.next;
    }
    //注意这个链表找中点的 两个指针初始的值
    public ListNode midNode(ListNode h){
       ListNode slow  = h;
       ListNode fast = h.next;
       while(fast!=null && fast.next!=null){
           slow = slow.next;
           fast = fast.next.next;
    }
           return slow;
    }
}

快速排序

//荷兰国旗和按某数分左中右,快排是这个的加强
//<,=,>
public class QuickSort {

	public static void quickSort(int[] arr) {
		if(arr==null|| arr.length<2) {
			return;
		}
		quickSort(arr,0,arr.length-1);
	}
	/**
     * @param sourceArray:需要排序的数组
     * @param left:排序数组左边界,一般为:0
     * @param right:排序数组右边界,一般为:length - 1;
     * less:小于参照元素区域的最右边边界:less = p[0] - 1;
     * more:大于参照元素区域的最左边边界:more = p[1] + 1;
     * p[0]:等于参照元素区域的最左边边界;
     * p[1]:等于参数元素区域的最右边边界;
     * 小于参照元素区域:[Left ~ less];
     * 等于参照元素区域:[p[0] ~ p[1]];
     * 大于参照元素区域:[more ~ right];
     */
	public static void quickSort(int[] sourceArray,int left,int right ) {
		if (left < right) {
            swap(sourceArray, left + (int) (Math.random() * (right - left + 1)), right);
            // p 数组中: p[0] 表示等于区域的左边界,p[1] 表示等于区域的右边界,
            // 左边区域:L ~ p[0] - 1;右边区域: p[1] + 1 ~ R;
            int[] p = partition(sourceArray, left, right);
            quickSort(sourceArray, left, p[0] - 1);
            quickSort(sourceArray, p[1] + 1, right);
        }
	}
	public static int[] partition(int[] sourceArray, int left, int right) {
        int less = left - 1;
        int more = right;
        int cur = left;
        while (cur < more) {
            // 以数组最后一个元素为标准,将整个数组划分为 小于、等于、大于 三个部分
            if (sourceArray[cur] < sourceArray[right]) {
                swap(sourceArray, ++less, cur++);
            } else if (sourceArray[cur] > sourceArray[right]) {
                swap(sourceArray, --more, cur);
            } else {
                cur++;
            }
        }
        swap(sourceArray, more, right);
        return new int[]{less + 1, more};
    }
	public static void swap(int[] sourceArray, int left, int right) {
        int tmp = sourceArray[left];
        sourceArray[left] = sourceArray[right];
        sourceArray[right] = tmp;
    }
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr = {43, -31, 10, -38, -42, -2, 22, 29, 30, 15, -60, -50, -13, 26, 3, 22, 27, 24, 18, 18, 42, -40, 22, 8, 33, -52, -70, -55, 31, 42, 82, 19, -8, 8, 41, -35, 59, 65, -23, 3, -34, 65};
        System.out.println("原数组为:");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "  ");
        }

        quickSort(arr);
        System.out.println("\n排序后数组为:");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "  ");
        }

	}

Top K

快排+ 堆
第K个,前K个,都可以用快排的思想来做,
快排思想

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
    if (k == 0) {
        return new int[0];
    } else if (arr.length <= k) {
        return arr;
    }
    
    // 原地不断划分数组
    partitionArray(arr, 0, arr.length - 1, k);
    
    // 数组的前 k 个数此时就是最小的 k 个数,将其存入结果
    int[] res = new int[k];
    for (int i = 0; i < k; i++) {
        res[i] = arr[i];
    }
    return res;
}

void partitionArray(int[] arr, int lo, int hi, int k) {
    // 做一次 partition 操作
    int m = partition(arr, lo, hi);
    // 此时数组前 m 个数,就是最小的 m 个数
    if (k == m) {
        // 正好找到最小的 k(m) 个数
        return;
    } else if (k < m) {
        // 最小的 k 个数一定在前 m 个数中,递归划分
        partitionArray(arr, lo, m-1, k);
    } else {
        // 在右侧数组中寻找最小的 k-m 个数
        partitionArray(arr, m+1, hi, k);
    }
}

// partition 函数和快速排序中相同,具体可参考快速排序相关的资料
// 代码参考 Sedgewick 的《算法4》
int partition(int[] a, int lo, int hi) {
    int i = lo;
    int j = hi + 1;
    int v = a[lo];
    while (true) { 
        while (a[++i] < v) {
            if (i == hi) {
                break;
            }
        }
        while (a[--j] > v) {
            if (j == lo) {
                break;
            }
        }

        if (i >= j) {
            break;
        }
        swap(a, i, j);
    }
    swap(a, lo, j);

    // a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
    return j;
}

void swap(int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}
}

堆的做法

class Solution {
    //维护一个以出现次数为key的 小根堆(K)那么大,因此有可以随时去掉小的,维持K个最大的
    //所以这个小根堆 以 一个数组为元素,数组里面是数字和对应的次数
    //统计次数用hasnmap,
    public int[] topKFrequent(int[] nums, int k) {
     HashMap<Integer,Integer> map = new HashMap<>();
        for(int i =0;i<nums.length;i++){
            map.put(nums[i],map.getOrDefault(nums[i],0)+1);
        }
        //小根堆
        PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] o1, int[] o2){
                 return o1[1]-o2[1];
            }
        });
        //遍历map,把元素加进堆了,注意,堆只能有K个元素
        for(Map.Entry<Integer,Integer> entry: map.entrySet()){
            if(heap.size() == k){
                if(heap.peek()[1] < entry.getValue()){
                    heap.poll();
                    heap.offer(new int[]{entry.getKey(),entry.getValue()});
                }
            }else{
                heap.offer(new int[]{entry.getKey(),entry.getValue()});
            }
        }
        //堆转到数组中
        int[] res = new int[k];
        for(int i=0;i<k;i++){
            res[i] = heap.poll()[0];
        }
        return res;
    }
}

数组

283 移动零 && 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

这两个题都是快排思想的应用,只是指针都可以简化,未必都需要三个指针;
移动0. 快排思想的简化版,直接不等于0的往前移动,后面的全都置为0;

class Solution {
    //划分区域,想借鉴快排的思想,用一个指针,相当于left记录不为0区域的当前位置
    //每次遇到不为0的就放在left++的位置
    public void moveZeroes(int[] nums) {
        if(nums==null||nums.length==0) return;
        int left = -1;
        int cur  =0;
        while(cur<nums.length){
            if(nums[cur]!=0){
                nums[++left] = nums[cur];
            } 
            cur++;
        }
        while(left<nums.length-1){
            nums[++left] =0;
        }
    }
}

剑指offer21

class Solution {
    //想法还是快排的partition的思路,可以当作只有小于和大于没有等于区
    public int[] exchange(int[] nums) {
        int left = 0, right = nums.length-1;
        while(left < right){
           if((nums[left] & 1 )== 0){
               //注意&要加括号
               swap(nums, left,right--);
           }else{
               left++;
           }
        }
        return nums;
    }
    public void swap(int[] nums, int i,int j){
        int tmp = nums[j];
        nums[j] = nums[i];
        nums[i] = tmp;
    }
}

剑指offer003 数组中重复的数字

//方法一:借助哈希结构
	public static boolean findDuplicate(int[] arr) {
		if(arr.length==0) {
			return false;
		}
		HashSet<Integer> set = new HashSet<Integer>();
		for(int i=0;i<arr.length;i++) {
			
			if(set.contains(arr[i])) {
				System.out.println(arr[i]);
				return true;
			}
			set.add(arr[i]);
		}
		return false;
	}
	//方法二:不使用额外的空间
	public static boolean find(int[] arr) {
		if(arr.length==0) {
			return false;
		}
		for(int i=0;i<arr.length;i++) {
		//比如index= 3,存的数字是4,那就把这个数字放在4这个位置上,然后把换出来的数字继续交换
			while(arr[i] !=i) {
				if(arr[i] == arr[arr[i]]) {
					System.out.print(arr[i]);
					return true;
				}else {
					swap(arr,i,arr[i]);
				}
			}
		}
		return false;
	}
	public static void swap(int[] arr,int i,int j) {
		int temp = arr[i];
		arr[i]= arr[j];
		arr[j] = temp;
	}

剑指 Offer 56 - I. 数组中数字出现的次数

剑指 Offer 04. 二维数组中的查找

/**
 * 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
 * 完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
 */
public class T4FindInArray {
    public static boolean hasNum(int[][] arr,int num) {
    	int row = 0;
    	int col = arr[0].length-1;
//    	int i=0; 
//    	int j = col-1;
    	while(row <= arr.length-1 && col>=0) {
    		if(arr[row][col]==num) {
    			return true;
    		}
    		if(arr[row][col]<num) {
    			row++;
    		}else if(arr[row][col]>num) {
    			col--;
    		}
    	}
    	return false;
    }

顺子

public boolean isStraight(int[] nums) {
        Arrays.sort(nums);
        int count = 0;
        for(int i = 0; i < nums.length-1; i++){
            if(nums[i] == 0){
                count++;
                continue;
            }
            if(nums[i] == nums[i+1]) return false;
            int tmp = nums[i+1] - nums[i];
            while(tmp > 1){
                if(count == 0) return false;
                count--;
                tmp--;
            }
        }
        return true;

    }

山型数组

public boolean validMountainArray(int[] A) {
        if (A == null || A.length < 3) {
            return false;
        }
        int n = A.length;
        int low = 0, high = n - 1;
        while (low < high && A[low] < A[low + 1]) {
            low++;
        }
        while (low < high && A[high] < A[high - 1]) {
            high--;
        }
        if (low == n - 1 || high == 0) {
            return false;
        }
        return low == high ? true : false;
    }

跳跃游戏

一个错误解法,就是想顺着找,也不知道为啥不对

public boolean canJump(int[] nums) {
        boolean res = false;
        for(int i = 0;i<nums.length-1;i++){
            if(nums[i] == 0) break;
            int index = i;
            while(index < nums.length){
                if(nums[i] == 0) break;
                index += nums[i];
                if(index > nums.length) break;
                if(index == nums.length){
                    res = true;
                }
            }
        }
        return res;
    }

贪心解法:就是不是一定要跳到那个点才叫到达,只要在最大区间里面的点,都可以成为到达。

class Solution {
    //为什么可以想到贪心呢,因为它不是要精确的跳到呢个点,而是只要那个点在可以跳到的范围内就行
    //所以就是尽可能的 远 就行
    public boolean canJump(int[] nums) {
        int max = 0;
        for(int i = 0;i< nums.length;i++){
            if(i>max) return false;/跳不到这个点,后面的就更到不了了
            max = Math.max(i+nums[i],max);
            if(max >= (nums.length-1)) return true;
        }
        return false;

    }
}

二维无序数组找第一个不存在自然数

541最短无序连续子数组

两数之和

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null, tail = null;
        int carry = 0;
        while (l1 != null || l2 != null) {
            int n1 = l1 != null ? l1.val : 0;
            int n2 = l2 != null ? l2.val : 0;
            int sum = n1 + n2 + carry;
            if (head == null) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail.next = new ListNode(sum % 10);
                tail = tail.next;
            }
            carry = sum / 10;
            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        if (carry > 0) {
            tail.next = new ListNode(carry);
        }
        return head;
    }
}

三数之和

public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new LinkedList<>();
        if(nums.length<3) return res; 
        Arrays.sort(nums);
        for(int i=0;i<nums.length;i++){
            int left = i+1;
            int right = nums.length-1;
            if(i>0 && nums[i]==nums[i-1]) continue;
            while(left<right){
                int sum = nums[left]+nums[i]+nums[right];
                if(sum==0){
                    res.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    while(left<right && nums[left+1]==nums[left]) left++;
                    while(left<right && nums[right-1]==nums[right]) right--;
                    left++;
                    right--;
                }else if(sum<0) left++;
                else right--;  
            }
        }
        return res;
    }

最接近的三数和

class Solution {
    //觉得跟直接找三数之和的那个提差不多
    //先排序,再对每个数字后面用双指针
    //当然如果这个数字本身已经大于target,就不用比较了
    public int threeSumClosest(int[] nums, int target) {
        int res = Integer.MAX_VALUE;
        int ress = Integer.MAX_VALUE;
        Arrays.sort(nums);
        for(int i = 0;i<nums.length;i++){
            int l = i+1;
            int r = nums.length-1;
            while(l<r){
                int num = nums[i]+nums[l]+nums[r];
                if(Math.abs(num-target) < res) {
                   res = Math.abs(num-target);
                   ress = num;
                }
                if(num<=target) l++;
                if(num>=target) r--;
            } 
        }
        return ress;
    }
}

41 缺失的最小正数 ⭐️

给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

class Solution {
    //1 想清楚 一个 长度为n的数组,最小的没有出现的正数,最大是n+1,区间就是[1,n+1]
    //2 剩下的就是没有出现,hash很正常,数组变hash也常见,一般以index为key,数组中的值为val;
    //这里 如果不是追求 O(1)的空间没必要改原数组,再new个数组,也不用改负数啥的了
    public int firstMissingPositive(int[] nums) {
        int len = nums.length;
        //遍历负改大
        for(int i = 0; i < len; i++){
            if(nums[i] <= 0) nums[i] = len+2;
        }
        //再遍历,这里要注意啦,值是不可信的,因为可能在这次遍历的过程中改了
        for(int i = 0; i < len; i++){
            int tmp = Math.abs(nums[i]);
            if(tmp <= len){
                int index = tmp - 1;
                nums[index] = -Math.abs(nums[index]);
                            }
        }
        //找结果
        //int res = len + 1;
        for(int i = 0; i < len; i++){
            if(nums[i] >= 0){
                return i+1;
            }
        }
        return len+1;


    }
}

287 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

class Solution {
    //用set好简单, 但空间不满足
    //数组 改成map, 下标作为key,出现就把对应下标的值改成负的, 若想改时发现已经负了, 就是这个数
    public int findDuplicate(int[] nums) {
        int len = nums.length;
        int res = 0;
        for(int i = 0; i < len; i++){
            int num = Math.abs(nums[i]);
            if(nums[num] < 0){
               res = num;
               break;
            }
            else{
                nums[num] = -nums[num];
            }
        }
        return res;


    }
}

这种缺失,重复 不是用map(或者以数组为map,就是用 位运算)

442 数组中重复的数据

给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。

找到所有出现两次的元素。

你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?

class Solution {
    //set最简单但肯定不让用
    //另一个做法 抽屉原理
    public List<Integer> findDuplicates(int[] nums) {
        //不交换 ,改为负
        List<Integer>  res = new ArrayList<>();
        if(nums == null ||nums.length == 0) return res;
        for(int i = 0; i < nums.length; i++){
            int num = Math.abs(nums[i]);
            if(nums[num-1] < 0){
                res.add(num);
                continue;
            }else{
                nums[num-1] = -nums[num-1];
            }
        }
        return res;

    }
}

一样的,没啥意思,看看41 得了

剑指 正整数数组组成的最大的数()

快手一面,正整数数组,串成最大的数
class Solution {
    //这题按自己的思路是把数字排序,如果第一位相等再去比较第二位,但是很难实现
    //那入股考虑把数字拼成字符串呢?为什么要这样考虑? 如果直接拼成一个最大的数,干嘛还要你返回一个字符串,既然是字符串
    //那就直接考虑成字符串,两个字符串比较的 compareTo方法,不就是相比较第一位,第一位相等再比较第二位
    //返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和      参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。
    public String largestNumber(int[] nums) {
        //先转string
        String[] strNums = new String[nums.length];
        for(int i=0;i<nums.length;i++){
            strNums[i] = String.valueOf(nums[i]);
        }
        //其实通过写这个就可以实现一个String数组按ascii码来排序,然后再拼接起来就行了
        Arrays.sort(strNums,new Comparator<String>(){
            public int compare(String a,String b){
                String str1 = a+b;
                String str2 = b+a;
                //核心就是按两个字符串起来哪个更大来排序
                //a-b;升序,b-a降序
                return str2.compareTo(str1);
            }
        });
        //如果第一位是0,就直接return0就行了
        if(strNums[0].equals("0")) return "0";
        StringBuilder sb = new StringBuilder();
        for(int i =0;i<strNums.length;i++){
            sb.append(strNums[i]);
        }
        return sb.toString();

    }
}

31 下一个排列

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须 原地 修改,只允许使用额外常数空间。

class Solution {
    //贪心
    /**
       1 要找字典序更大的排列 -> 低位大数换高位小数 -> 左小和右大交换
       2 大的尽量少才是下一个排列 -> 交换的过程尽量在右边 
                              右边大数中较小的那个, 换完之后,对右边的数升序排序
     具体做法,从后往前找第一个升序对,i,j,那么j之后的必然是降序排的
     此时小的数是最左的,大的数是构成升序最右的,却未必是最合适的,
     真正适合交换的, 是 从这个最右的大数往右看 最小的那个
     交换之后,后面的数 此时是逆序的, 本来就是降序,又把最小的那个换到最右了,此时把后面的数字逆序即可          
    */
    public void nextPermutation(int[] nums) {
        
        int len = nums.length;
        if(len == 1) return;
        int l = -1, r = -1;
        for(int i =len-1; i>=0; i--){
            if( l!=-1 && r!= -1) break;
            for(int j = i; j < len; j++){
                if(nums[i] < nums[j]){
                    l = i;
                    r = j;
                    break;
                }
            }
        }
        if(l == -1 || r == -1){
            reverseNums(nums,0,len-1);
        }else{
            int k = -1;
            for(int i = len-1; i >= r; i--){
                if(nums[i] > nums[l]){
                    k = i;
                    break;
                }
            }
            swap(nums, l, k);
            reverseNums(nums,r,len-1);
        }

    }
    public void swap(int[] nums, int l, int r){
        int tmp = nums[l];
        nums[l] = nums[r];
        nums[r] = tmp;
    }
    public void reverseNums(int[] nums, int l, int r){
        while(l <= r){
            int tmp = nums[l];
            nums[l] = nums[r];
            nums[r] = tmp;
            l++;
            r--;
        }
    }
}

所以即使是在递归过程中,也要及时判断条件是否满足,尽量减少递归次数

class Solution {
    //DFS,
    // 矩阵中每个点都可以作为开始结点
    // 用过的点不能再用,所以要加标记,还要在回溯回来的过程中取消标记
    //超时怎么办,就尽量减少递归次数
    boolean res = false;
    public boolean exist(char[][] board, String word) {
        if(board == null || word == null) return false;
        char[] chars = word.toCharArray();
        int len = chars.length;
        int m = board.length;
        int n = board[0].length;
        int[][] used = new int[m][n];
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                //不需要所有的都遍历,只要找到一个true就可以立即返回
                if(board[i][j] == chars[0]){
                   dfs(board,chars,0,i,j,used);
                }
                if(res){
                    return true;
                }

            }
        }
        return false;
    }
    public void dfs(char[][] board, char[] chars, int index,int i, int j,int[][] used){
        if(index == chars.length){
            res = true;
            return ;
        }
        if(i<0 || i>board.length-1|| j<0 || j>board[0].length-1) return ;
        if(used[i][j] == 1) return ;
        if(board[i][j] !=  chars[index]) return ;
        used[i][j] = 1;
        //这里也要随时判断来减少递归的次数
        dfs(board,chars,index+1,i+1,j,used); 
        if(res) return;
        dfs(board,chars,index+1,i-1,j,used);
        if(res) return;
        dfs(board,chars,index+1,i,j+1,used);
        if(res) return;
         dfs(board,chars,index+1,i,j-1,used);
         if(res) return;
        used[i][j] = 0;
    }
}

剑指66 构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

两个数组,一个记录前几个的乘积,一个记录后面的乘积

class Solution {
    public int[] constructArr(int[] a) {
        int len = a.length;
        if(len == 0) return new int[]{};
        int[] pre = new int[len];
        pre[0] = a[0];
        int[] post = new int[len];
        post[len-1] = a[len-1];
        //if(len == 0 || a == null) return new int[];
        for(int i = 1; i < len; i++){
            pre[i] = pre[i-1] * a[i];
        }
        for(int i = len-2; i >= 0; i--){
            post[i] = post[i+1] * a[i];
        }
        int[] res = new int[len];
        res[0] = post[1];
        res[len-1] = pre[len-2];
        for(int i = 1; i < len-1; i++){
            res[i] = pre[i-1] * post[i+1];
        }
        return res;
    }
}

73矩阵置0(标记的两种方法)

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。空间常数。

class Solution {
    /**
    实际上这题不知道在考啥? 遍历一遍,记录下标就好,再一次遍历改0
    只是这样子分行列数组的话,空间不太优化,所以可以加标记:
    1-- 第一次遍历时,可以把0所在的行和列设置为 非常大或者非常小的值,第二次直接改
    2-- 第一次遍历时可以 把 0所在的行列的头标记一下,第二次遍历就直接遍历第一列和第一行
    */
    public void setZeroes(int[][] matrix) {
        int R = matrix.length;
    int C = matrix[0].length;
    Set<Integer> rows = new HashSet<Integer>();
    Set<Integer> cols = new HashSet<Integer>();

    // Essentially, we mark the rows and columns that are to be made zero
    for (int i = 0; i < R; i++) {
      for (int j = 0; j < C; j++) {
        if (matrix[i][j] == 0) {
          rows.add(i);
          cols.add(j);
        }
      }
    }

    // Iterate over the array once again and using the rows and cols sets, update the elements.
    for (int i = 0; i < R; i++) {
      for (int j = 0; j < C; j++) {
        if (rows.contains(i) || cols.contains(j)) {
          matrix[i][j] = 0;
        }
      }
    }
  }



    
}

128 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
进阶:你可以设计并实现时间复杂度为 O(n) 的解决方案吗?

class Solution {
    //如果不要求时间复杂度,可以排序后从前往后遍历即可
    public int longestConsecutive(int[] nums) {
        if(nums ==  null) return 0;
        int len = nums.length;
        if(len == 1) return 1;
        Arrays.sort(nums);
        int res = 0;
        int cur = 1;
        int tmp ;
        while(cur < len){
            tmp = 1;
            while(cur < len && (nums[cur]== nums[cur-1]+1 || nums[cur] == nums[cur-1])){
                if(nums[cur] != nums[cur-1]) tmp++;
                cur++;   
            }
            cur++;
            res = Math.max(res, tmp);
        }
        return res;
        
    }
}

如果时间复杂度是线性的:
空间换时间,跳过的思路跟做法一是一样的。
甚至这里解法一的时间性能要比二好。。。。估计跟测试用例的数据有关。。

class Solution {
    //如果要求线性的复杂度,弄个辅助数组,把数字存在对应的下标里,只是这里数组会非常非常大,不合适
    //另一个思路, hashset,然后每个值去列举以它为开始的长度
    //按照排序的思路,跳过,也就是 像上个思路一样, 从x开始往后找,直到它不连续,那么下一次从这个不连续的开始找,而不是从x+1开始
    //因为从x+1开始只能找到比从x开始小一个的,所以跳过,用hash辅助是同理的
    public int longestConsecutive(int[] nums) {
        if(nums ==  null) return 0;
        int len = nums.length;
        if(len == 1) return 1;
        int res = 0;
        // 一:空间换时间
        Set<Integer> dict = new HashSet<>();
        for(int num: nums) dict.add(num);
        //二:减少没必要的比较
        for(int i = 0; i < len; i++){
            //以x为开始的区间,已经找到,这个区间内的每一个值,都没必要再找,所以,要跳过这个区间,特征就是:
            if(dict.contains(nums[i]-1)) continue;
            int tmpLen = 1;
            int tmpNum  = nums[i] + 1;
            while(dict.contains(tmpNum)){
                tmpLen ++;
                tmpNum++;
            }
            res = Math.max(tmpLen, res);
        }
        return res;
    }
}

238 除自身以外数组的乘积

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

class Solution {
    //两个数组,一个从前往后,一个从后往前,或者一个数组先从前往后,再从后往前
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        if(n == 0) return new int[0];
        if(n == 1) return nums;
        int[] preProduct = new int[n];
        int[] postProduct = new int[n];
        preProduct[0] = 1;
        postProduct[n-1]  = 1;
        for(int i = 1; i < n; i++){
            preProduct[i] = preProduct[i-1] * nums[i-1];
        }
        for(int i = n-2; i >= 0 ; i--){
            postProduct[i] = postProduct[i+1] * nums[i+1];
        }
        for(int i = 0; i < n; i++){
            preProduct[i] = preProduct[i] * postProduct[i];
        }
        return  preProduct;
    }
}

优化: 降低空间复杂度,现在除了用于输出的preProduct,还有一个post,所以空间是n。
优化可以一个用数组,另外一个用一个整数迭代着表示即可

class Solution {
    //两个数组,一个从前往后,一个从后往前,或者一个数组先从前往后,再从后往前
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        if(n == 0) return new int[0];
        if(n == 1) return nums;
        int[] preProduct = new int[n];
        int postProduct = 1;
        preProduct[0] = 1;
        //前向数组
        for(int i = 1; i < n; i++){
            preProduct[i] = preProduct[i-1] * nums[i-1];
        }
        //后向用一个整数保存,并且将前后乘积一起放在 preProduct里面
        for(int i = n-2; i >= 0 ; i--){
            postProduct = postProduct * nums[i+1];
            preProduct[i] = postProduct * preProduct[i];
        }
        // for(int i = 0; i < n; i++){
        //     preProduct[i] = preProduct[i] * postProduct[i];
        // }
        return  preProduct;
    }
}

整数

9 回文数

解法一: 转为字符串类型,然后再去判断是否是回文结构
可以去回顾一下String StringBuffer StringBuilder

public boolean isPalindrome(int x) {
        String reversedStr = (new StringBuilder(x + "")).reverse().toString();
        return (x + "").equals(reversedStr);
    }

解法二: 从回文的定义出发,翻转后半部分数字,然后再比对

public boolean isPalindrome(int x) {
        if(x<0 || (x%10==0 && x!= 0)) return false;
        int reverseNum = 0;
        //怎么确定翻转到了一半?
        //若是回文数,那么翻转一半后半部分的数要大于(奇数位),等于(偶数位)前半部分
        while(x>reverseNum){
            int yu = x % 10;
            reverseNum = reverseNum * 10 + yu;
            x = x / 10;
        }
        //若为奇数位个数,那么后半部分会比前半部分多一个尾数
        return x==reverseNum || x == reverseNum/10;
    }

7 整数反转⭐️

回文数算法思想相同,但需要判断32位整数是否溢出(溢出分正负考虑即可),因为int类型占4个字节,取值范围为:-2147483648~2147483647,所以有可能出现原数为1999999999,如果反转就溢出了。

public int reverse(int x) {
        int reverseNum = 0;
        while(x != 0){
            int pop = x % 10;
            x = x / 10;
            //正数溢出的情况
            if(reverseNum>Integer.MAX_VALUE/10 || (reverseNum==Integer.MAX_VALUE/10 && pop>7)) return 0;
            //负数
            if(reverseNum<Integer.MIN_VALUE/10 ||(reverseNum==Integer.MIN_VALUE/10 && pop<-8)) return 0;
            reverseNum = reverseNum * 10 +pop;
        }
        return reverseNum;
    }

13 罗马数转整数

大数在左 小数在右,若不符合,则要相减

public int romanToInt(String s) {
        if(s==null) return 0;
        int res = 0;
        int preNum = getValue(s.charAt(0));
        //大数在左 小数在右,若不符合,则要相减
        for(int i = 1;i<s.length();i++){
            int num  = getValue(s.charAt(i));
            if(preNum < num) res -= preNum;
            else res += preNum;
            preNum = num;
        }
        //别忘了最后一个
        res+= preNum;
        return res;

    }
     private int getValue(char ch) {
        switch(ch) {
            case 'I': return 1;
            case 'V': return 5;
            case 'X': return 10;
            case 'L': return 50;
            case 'C': return 100;
            case 'D': return 500;
            case 'M': return 1000;
            default: return 0;
        }
     }

69 x的平方根

如果使用for循环,在小于等于x的范围内逐个找值的话,虽然是一种解决办法,但是运行会超出时间限制。

计算平方根最好的办法是牛顿迭代法:

 public int mySqrt(int x) {
        if (x == 0) {
            return 0;
        }

        double C = x, x0 = x;
        while (true) {
            double xi = 0.5 * (x0 + C / x0);
            if (Math.abs(x0 - xi) < 1e-7) {
                break;
            }
            x0 = xi;
        }
        return (int)x0;
    }

当然也可以考虑二分法:对0到x二分,查找第一个平方大于等于x的数

 public int mySqrt(int x) {
        int l = 0, r = x, ans = -1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if ((long)mid * mid <= x) {
                ans = mid;
                l = mid + 1;
            }
            else {
                r = mid - 1;
            }
        }
        return ans;
    }

279. 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少

public int numSquares(int n) {
        //状态: 1-n的数字的最少完全平方数
        int[] dp = new int[n+1];
        //取最小的,初始化时可以用最大值
        Arrays.fill(dp, Integer.MAX_VALUE);
        //平方数表
        int count  = (int)Math.sqrt(n)+1;
        int[] sqrtNum = new int[count];
        for(int i = 0;i< count;i++){
            sqrtNum[i] = i*i;
        }
        //初始化
        dp[0] = 0;
        for(int i=1;i<=n;i++){
            for(int j = 1;j<count;j++){
                if(sqrtNum[j] > i) break;
                else{
                    dp[i] = Math.min(dp[i],dp[i- sqrtNum[j]]+1);
                }
            }
        }
        return dp[n];
    }

461 汉明距离

二进制的不同位数: 可以逐位异或来求

public int hammingDistance(int x, int y) {
        return Integer.bitCount(x ^ y); 
    }

将x,y按位异或得到i,将问题转化为求i的二进制位中1的个数count。异或后移位,根据1的个数,判断有几个不同。

 public int hammingDistance(int x, int y) {
        int i = x ^ y;
        int count = 0;
        while(i != 0){
            //
            if((i & 1) == 1)
               count++;
            i = i >> 1;//右移动一位
        }
        return count;

470. 用 Rand7() 实现 Rand10()⭐️

题目描述:已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。
不要使用系统的 Math.random() 方法。

大到小可以直接用,直到生成满足范围的数,因为是等概率的;
小到大,用加法(如这里的 7+7-1,不能够等概率),先用乘法转成比大数大的,然后就变成大到小了
比如:
rand7 -》rand10 :(rand7() -1)*7 + rand7();
rand5->rand7 : (rand5-1)*5 + rand 5()
总之: 小到大: (randX()-1) * X +randX(); 然后再用方法一或者方法二;

首先 rand7()-1得到的数的集合为 { 0,1,2,3,4,5,6 }
再乘 7 后得到的集合 为{0,7,14,21,28,35,42}
后面 rand7()得到的集合B为{1,2,3,4,5,6,7}
相加后得到的是1-49的等概率的数
然后就变成了 rand49 到 rand10,大到小,只要把大数转成小的即可

其实是数据转换的操作,但是要保证等概率的问题
方法一:

 public int rand10() {
       int num = (rand7() - 1) * 7 + rand7();
       // 只要它还大于0,那你就给我不断生成吧
       while (num > 10)
           num = (rand7() - 1) * 7 + rand7();
       // 返回结果,+1是为了解决 40%10为0的情况
       return num;
    }

方法一:我们的函数会得到 1~49 之间的数,而我们只想得到 1~10之间的数,这一部分占的比例太少了,
简而言之,这样效率太低,太慢,可能要 whilewhile 循环很多次,那么解决思路就是舍弃一部分数,
舍弃 41~49,因为是独立事件,我们生成的 1~40之间的数它是等概率的,我们最后完全可以利用 1~40之间的数来得到 1~10之间的数。

也就是方法一中浪费了计算资源,所以不让只要满足10 就返回,而是满足40就返回

public int rand10() {
    // 首先得到一个数
    int num = (rand7() - 1) * 7 + rand7();
    // 只要它还大于40,那你就给我不断生成吧
    while (num > 40)
        num = (rand7() - 1) * 7 + rand7();
    // 返回结果,+1是为了解决 40%10为0的情况
    return 1 + num % 10;
}

快速幂⭐️

快速幂
class Solution {
    //递归,相当于指数二分的做法。知识要注意,N为奇数和偶数,
    public double myPow(double x, int n) {
        long N = n;
        return N >=0? quickMul(x,n):1.0/quickMul(x,-n);

    }
    public double quickMul(double x, long n){
        if(n==0) return 1.0;
        //这个过程是,9/2=4,除了第一个数,中间都是偶数,而且到了最后1,因为都是1,
        double y = quickMul(x,n/2);
        return n%2==0? y*y:y*y*x;
        //实际上,是奇数,只有第一个数和中间也是奇数的时候会额外乘x,最后得1才会用到。
        //例如77,38,19,9,用这种方式,奇数的时候都会多乘一个

    }

}

丑数

lass Solution {
    public int nthUglyNumber(int n) {
        int a = 0, b = 0, c = 0;
        int[] dp = new int[n];
        dp[0] = 1;
        for(int i = 1; i < n; i++) {
            int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
            dp[i] = Math.min(Math.min(n2, n3), n5);
            if(dp[i] == n2) a++;
            if(dp[i] == n3) b++;
            if(dp[i] == n5) c++;
        }
        return dp[n - 1];
    }
}

质因数分解

求出区间[a,b]中所有整数的质因数分解。

/**
 7  *  分析:对n进行分解质因数,应先找到一个最小的质数k
 8 
 9 * 最小的质数:即“2”。2是最小的质数,即是偶数又是质数,然后按下述步骤完成:
10 
11 *(1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,打印出即可。
12 
13 *(2)如果n>k,但n能被k整除,则应打印出k的值,并用n除以k的商,作为新的正整数你n,重复执行第一步。
14 
15 *(3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步。
16  * 
17  * @param 想想
18  */
19     public static void main(String []args){
20         Scanner s=new Scanner(System.in);
21         System.out.println("键入一个正整数:");
22         int n=s.nextInt();
23         int  k=2;  //定义一个最小的质因数 
24         System.out.print(n+"="); 
25         while (k<=n){    //进行循环判断   
26             if(k==n){ 
27                 System.out.print(n);
28                 break;
29             }else if (n%k==0){  
30                 System.out.print(k+"*");
31                 n=n/k;
32             }else k++; 
33         }
34     }

字符串

字符串常用方法:

1.int length();  //注意:字符串中是length()方法,数组中是length属性
2.char charAt(int index);  //取字符串中索引为index的字符元素
3.String indexOf(String str2);  //该方法在str1中检索str2,返回其在str1中第一次出现的位置索引,若找不到则返回-1。
4.String substring(int beginIndex, int endIndex);  //截取索引为 [beginIndex,endIndex) 的字符串(左闭右开)
5.char[] toCharArray();  //把字符串转化为字符数组
6.System.arraycopy(原数组,原数组的开始位置,目标数组,目标数组的开始位置,拷贝的元素个数);  //(二/6)属于java.lang.System包中。
7.Arrays.sort(nums);  //(二/6)关于Arrays类中封装的排序方法(采用归并排序)。
//我在java api中整理了一些常用的,这里出现的数组类型都以int作为示例,当然其他的基本数组类型都是通用的。(见下图)
8.String replace(char oldChar, char newChar);  //(一/5)替换字符串中字符或者字符串,char也可以是String
9. String valueOf(T);//各种类型转字符串类型

常用算法:滑动窗口,双指针,DP等

双指针

28. 实现 strStr()⭐️

很基础的双指针,要牢牢记住哈~
双指针方法或者直接使用 str1.indexOf(str2),直接用API解决;

public int strStr(String haystack, String needle) {
        int L1 = haystack.length();
        int L2 = needle.length();
        if(L2 == 0) return 0;
        int p1 = 0;
        while(p1 < L1-L2+1){
            while(p1< L1-L2+1 && haystack.charAt(p1) != needle.charAt(0)) p1++;
            int curLen = 0, p2 = 0;
            while(p1<L1 && p2<L2 && haystack.charAt(p1)==needle.charAt(p2)){
                p1++;
                p2++;
                curLen++;
            }
            if(curLen == L2) return p1-L2;
            //如果不符合,从下一个位置开始
            p1 = p1- curLen +1;
        }
        return -1;
    }

344. 反转字符串 541 反转字符串II ⭐️

反转字符串: 双指针(就像判断回文串的做法)\

 public void reverseString(char[] s) {
        int left = 0,right = s.length-1;
        while(left <= right){
            char c = s[left];
            s[left] = s[right];
            s[right] = c;
            left++;
            right--;
        }
    }

541 反转字符串II 给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。
只需要找到每次需要反转的开始和结束的位置,再利用如344的双指针做法即可;

public String reverseStr(String s, int k) {
        if(s==null || s.length()==0) return s;
        int start = 0;
        int len  = s.length();
        char[] chars = s.toCharArray();
        while(start<len){
            //如果剩余字符少于 k 个,则将剩余字符全部反转。
            if(len-start < k) reverse(chars,start,len-1);
            //2k中的前k个
            else reverse(chars,start,start+k-1);
            //start的下一个位置
            start = start+2* k;
        }
        return new String(chars);
    }
    public void reverse(char[] s,int start,int end){
        while(start <= end){
            char c = s[start];
            s[start] = s[end];
            s[end] = c;
            start++;
            end--;
        }
    }

49. 字母异位词分组

这里的点在于归类,就是abc ,bac怎么是一样的?就把它们转成字符数组,然后,,排序。
字母异位词指字母相同,但排列不同的字符串。
排序数组分类:排序后用map
Map集合中的三种方法:

values():获取集合中的所有的value值;

keySet():将Map中所有的key存放到Set集合中。应为Set集合有迭代器,可以通过迭代器循环key,在通过get()方法,得到每个key所对应的value;

entrySet():Set> entrySet() //返回此映射中包含的映射关系的 Set 视图。 Map.Entry表示映射关系。entrySet():迭代后可以e.getKey(),e.getValue()取key和value。返回的是Entry接口 。
 public List<List<String>> groupAnagrams(String[] strs) {
        if (strs.length == 0) return new ArrayList();
        Map<String, List> ans = new HashMap<String, List>();
        for (String s : strs) {
            char[] ca = s.toCharArray();
            Arrays.sort(ca);
            String key = String.valueOf(ca);
            //如果没有就先加进key,再加对应的value
            if (!ans.containsKey(key)) ans.put(key, new ArrayList());
            ans.get(key).add(s);
        }
        return new ArrayList(ans.values());
    }

3 无重复字符的最长子串

public int lengthOfLongestSubstring3(String s) {
	        Map<Character, Integer> map = new HashMap<Character, Integer>();
	       
	        int left = 0;
	        int res = 0;
	        int n = s.length();
	        for(int i =0;i<n;i++){
	            if(map.containsKey(s.charAt(i))){
	            	//这里需要注意 abba就可以说明情况,有可能最开始出现的字符直到最后才出现重复的
	                left = Math.max(map.get(s.charAt(i))+1,left);
	            }
	            map.put(s.charAt(i),i);
	            res = Math.max(res, i-left+1);
	        }
	        return res;	    
	}

30

76

验证ip地址

public String validIPAddress(String IP) {
        String[] IP4Arr = IP.split("\\.",-1);
        if(IP4Arr.length == 4){
            return isIP4Arr(IP4Arr);
        }

        String[] IP6Arr = IP.split(":",-1);
        if(IP6Arr.length == 8){
            return isIP6Arr(IP6Arr);
        }

        return "Neither";
    }

    public String isIP4Arr(String[] IP4Arr){
        for(String ip : IP4Arr){
            if(ip.length() > 3 || ip.length() <= 0){
                return "Neither";
            }
            for(int i = 0 ;i < ip.length();++i){
                if(!Character.isDigit(ip.charAt(i))){
                    return "Neither";
                }
            }
            int num = Integer.parseInt(ip);
            if(num > 255 || String.valueOf(num).length() != ip.length()){
                return "Neither";
            }
        }
        return "IPv4";
    }

     public String isIP6Arr(String[] IP6Arr){
        for(String ip : IP6Arr){
            if(ip.length() > 4 || ip.length() <= 0){
                return "Neither";
            }
            for(int i = 0 ;i < ip.length();++i){
                char c = ip.charAt(i);
                if(!Character.isDigit(c) && !( 'a' <= c && c <= 'f') && !('A' <= c && c <= 'F')){
                    return "Neither";
                }
            }
        }
        return "IPv6";
    }

438. 找到字符串中所有字母异位词 ⭐️

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

滑动窗口(双指针)解决这类问题的思路;

1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
2、什么条件下,窗口应该暂停扩大,开始移动 left 缩小窗口?
3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?

public List<Integer> findAnagrams(String s, String p) {
        if(s == null || s.length() == 0) return new ArrayList<>();
        List<Integer> res = new ArrayList<>();
        Map<Character,Integer> dictT = new HashMap<>();
        for(int i=0;i<p.length();i++){
            int count = dictT.getOrDefault(p.charAt(i), 0);
            dictT.put(p.charAt(i),count+1);
        }
        int required = dictT.size();
        //定义两个指针
        int L = 0; int R = 0;
        //定义一个统计当前窗口的变量
        int formed = 0;
        Map<Character,Integer> windowCounts = new HashMap<>();
        while(R<s.length()){
            char c = s.charAt(R);
            if(dictT.containsKey(c)){
                int value  = windowCounts.getOrDefault(c,0);
                formed++;
                windowCounts.put(c,value+1);
                while(formed==required){
                    //这里来控制连续,不需要去挨个考虑是不是在其中。
                    //若有不符合条件的,这个不会满足
                    if(R-L+1 == required){
                          res.add(L);
                    }
                    //窗口向右滑动
                    char tmp = s.charAt(L);
                    if(dictT.containsKey(L)){
                        windowCounts.put(c,windowCounts.get(c)-1);
                        formed--;
                    }
                    L++;
                }
                 R++;
            }
        }
        return res;
    }

空格处理

面试题05. 替换空格⭐️

从后往前填数组即可:

public String replaceSpace(String s) {
        //统计修改后的长度
        int count = 0;
        for(int i = 0;i<s.length();i++){
            if(s.charAt(i) != ' ') count++;
            else count += 3;
        }
        char[] chars = new char[count];
        count = count-1;
        for(int i = s.length()-1;i>=0;i--){
            if(s.charAt(i)==' '){
                chars[count--] = '0';
                chars[count--] = '2';
                chars[count--] = '%';
            }else{
                chars[count--] = s.charAt(i);
            }
        }
        return new String(chars);
    }

58. 最后一个单词的长度

从后往前找:先把结尾处的空格处理掉,额外要注意的是数组下标要保证有效才可以;

 public int lengthOfLastWord(String s) {
        if(s==null || s.length()==0) return 0;
        int len = s.length();
        int index = len-1;
        int count = 0;
        //先处理掉结尾处的空格
        while(index >=0 && s.charAt(index) == ' '){
            index--;
        }
        //从后往前,只需要单独处理一下以空格结尾的即可
        while(index >=0){
            if(s.charAt(index)==' ') break;
            else{
                count++;
                index--;
            }
        }
        return count;
    }

8. 字符串转换整数 (atoi) ⭐️

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:
如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。

调用Character类的静态方法:**static boolean isDigit(char c)**可以判断当前字符是否为数字,如果为数字,再使用:int digit = c - ‘0’; 来实现字符转换为数字,从而可以使用:num = num * 10 + digit; 来实现将字符串转换为整数。
int的最大值:Integer.MAX_VALUE
int的最小值:Integer.MIN_VALUE

 public int myAtoi(String str) {
        if(str==null ||str.length()==0) return 0;
        int len = str.length();
        int index = 0;
        //去掉开头的空格
        while(index<len){
            if(str.charAt(index) != ' ') break;
            index++;
        }
        //正负号
        boolean negtative = false;
        if(index<len && str.charAt(index)=='-'){
            negtative = true;
            index++;
        }else if(index<len && str.charAt(index)=='+'){
            index++;
        }
        //组合数字
        int num = 0;
        while(index <len && Character.isDigit(str.charAt(index))){
            int digit = str.charAt(index) -'0';
            if(num > (Integer.MAX_VALUE - digit)/10)
                 return negtative ? Integer.MIN_VALUE:Integer.MAX_VALUE;
                 //这个是不是很熟悉
            else num = num * 10 + digit;
            index++;
        }
        return negtative ? -num:num;
    }

连接所有空格

个人认为可以参考字符串压缩的思路;

递归&DP

139. 单词拆分⭐️

这题可以很好的体现 递归与DP的关系,一个是从上往下,一个是从下往上
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

直接想到了回溯的做法:
回溯说到底是递归,只需要考虑当前位置的情况. 会超时,但递归的思想很重要。因为递归想清楚了,动态规划的状态转移方程也出来了。

public boolean wordBreak(String s, List<String> wordDict) {
        return word_Break(s,new HashSet(wordDict),0);
    }
    public boolean word_Break(String s, HashSet<String> hashSet,int start){
        //递归出口
        if(start == s.length()) return true;
        //递归只需要考虑当前位置的表示就可以了,考虑这个位置之后的每一个单词
        //每一步所有的可能,只要有一个满足,就可以返回true
        for(int end = start+1;end<=s.length();end++){
            if(hashSet.contains(s.substring(start,end)) && word_Break(s,hashSet,end))
                  return true;
        }
        return false;
    }

动态规划做法:
dp[n+1]: dp[i] 状态 表示子串截止位置为i,那么可以考虑所有小于i的位置j,看前j个和j到i分别是不是字典中的单词。

可以考虑跟上面递归的区别。

public boolean wordBreak(String s, List<String> wordDict) {
       HashSet<String> hashSet = new HashSet(wordDict);
       //状态, i位置子串是否可拆分
       boolean[] dp = new boolean[s.length()+1];
       //初始化
       dp[0] = true;
       //状态
       for(int i = 1;i<=s.length();i++){
           //可能的划分位置
           for(int j = 0;j<i;j++){
               if(dp[j] && hashSet.contains(s.substring(j,i))){
                  dp[i] = true;
                  break;
               }
           }
       }
       return dp[s.length()];
    }

其他

剑指offer67 把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

class Solution {
    public int strToInt(String str) {
        if(str == null || str.length() == 0) return 0;
        int len = str.length();
        int cur = 0;
        boolean flag = false;
        while(cur < len && str.charAt(cur) == ' '){
            cur++;
        }
        if(len == 0 || cur == len) return 0;
        int res = 0, bndry = Integer.MAX_VALUE / 10;
        if(str.charAt(cur) == '-') flag = true;
        if(str.charAt(cur) == '-' || str.charAt(cur) == '+') cur++;
        for(int i = cur; i < len; i++){
           if(str.charAt(i) < '0' || str.charAt(i) > '9') break;
            if(res > bndry || res == bndry && str.charAt(i) > '7')
                return flag == false ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            res = res * 10 + (str.charAt(i) - '0');
        }
        return flag == true ? -1*res : res;  
    }
}

43 字符串乘法

模拟竖式, 字符串加法也在里面了;
从后往前,记录进位,最后翻转;

class Solution {
    //模拟竖式
    public String multiply(String num1, String num2) {
        if (num1.equals("0") || num2.equals("0")) {
            return "0";
        }
        String res = "0";
        for(int i = num2.length()-1; i >= 0; i--){
            //建一个字符串,先补后面的0,再依次按位计算
            StringBuilder sb = new StringBuilder();
            for(int j = 0; j < num2.length()-1-i; j++){
                sb.append(0);
            }
            //按位算
            int m = num2.charAt(i) - '0';
            int carry = 0;
            for(int j = num1.length()-1 ;j>=0||carry >0 ;j--){
                 int n = j<0 ? 0: num1.charAt(j) - '0';
                 int tmp = n * m + carry;
                 int produce = tmp % 10;
                 sb.append(produce);
                 carry = tmp / 10;
            }
            res = addString(res, sb.reverse().toString());
        }
        return res;
    }

    public String addString(String num1, String num2){
        StringBuilder sb = new StringBuilder();
        int carry = 0;
        for(int i = num1.length()-1, j = num2.length()-1; i>=0 || j>=0 || carry>0; i--,j--){
            int x = i<0 ? 0: num1.charAt(i) - '0';
            int y = j<0 ? 0: num2.charAt(j) - '0';
            int tmp = x + y + carry;
            sb.append(tmp %10);
            carry = tmp /10;
        }
        return sb.reverse().toString();
    }
        
}

14. 最长公共前缀

最长公共前缀:数组中所有单词共有的,任意一个单词不包含了,就停了;

public String longestCommonPrefix(String[] strs) {
        if(strs==null || strs.length==0) return "";
        if(strs.length ==1 ) return strs[0];
        String s = strs[0];
        int i = 0;
        for(i=0;i<s.length();i++){
            char c = s.charAt(i);
            for(int j = 1;j<strs.length;j++){
                if(i==strs[j].length() ||  strs[j].charAt(i) != c)
                    return s.substring(0,i);
            }
        }
        return s;
    }

394. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。
倒是想得到栈的方法,可是写不出来呀~

辅助栈法:
如果当前的字符为数位,解析出一个数字(连续的多个数位)并进栈
如果当前的字符为字母或者左括号,直接进栈
如果当前的字符为右括号,开始出栈,一直到左括号出栈,出栈序列反转后拼接成一个字符串,此时取出栈顶的数字

栈继承自vector,最好不要用,可以像这里一样,用LinkedList或者双端队列(代替)

腾讯笔试题,跟leetcode上稍微有一点点不一样
栈方法

public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        String input = sc.nextLine();
        LinkedList<String> strs = new LinkedList<>();
        LinkedList<Integer> counts =new LinkedList<>();
        StringBuilder res = new StringBuilder();
        int times = 0;
        for(char c: input.toCharArray()){
            if(c=='['){
                strs.add(res.toString());
                res = new StringBuilder();
            }else if(c==']'){
                //右括号做两件事,一是 :把当前层的串重复几次,二是跟上一层的串在一起
                int count = counts.removeLast();
                StringBuilder tmp = new StringBuilder();
                for(int i=0;i<count;i++) tmp.append(res);
                res = new StringBuilder(strs.removeLast() + tmp);
             }else if(c=='|'){
                counts.add(times);
                times = 0;
            }else if(Character.isDigit(c)) times = times * 10 + c- '0';
            else res.append(c);
                }
        System.out.println(res.toString());
        }
}
import java.util.*;
public class Main {
     public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        String input = sc.nextLine();
         System.out.println(dfs(input,0)[0]);
     }
    public static String[] dfs(String s,int i){
        int times = 0;
        StringBuilder res= new StringBuilder();
        while(i < s.length()){
            char c = s.charAt(i);
            if(c=='['){
                String[] tmp = dfs(s,i+1);
                res.append(tmp[0]);
                i = Integer.parseInt(tmp[1]);
            }else if(c==']'){
                StringBuilder ss =new StringBuilder();
                for(int j=0;j<times;j++){
                    ss.append(res);
                }
                return new String[]{ss.toString(),i+""};
            }else if(Character.isDigit(c)){
                times = times* 10+ c-'0';
            }else if(Character.isLetter(c)){
                res.append(c);
            }else{
                i++;
                continue;
            }
            i++;
        }
        return new String[]{res.toString()};
    }
}

面试题 01.06. 字符串压缩

字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。

 public String compressString(String S) {
        if(S==null|| S.length()==0) return S;
        StringBuilder ss = new StringBuilder();
        int start = 0;
        while(start < S.length()){
            int end = start+1;
            int count = 1;
            ss.append(S.charAt(start));
            while(end< S.length() && S.charAt(end)==S.charAt(start)){
                count++;
                end++;
            }
            ss.append(count);
            start = end;
        }
        return String.valueOf(ss).length() >= S.length() ? S: String.valueOf(ss);

    }

844. 比较含退格的字符串

子串

3 无重复字符的最长子串

滑动窗口 + hashset

 public int lengthOfLongestSubstring(String s) {
        if(s==null || s.length()==0) return 0;
        int start = 0;
        int len = 1;
        while(start < s.length()){
            int end = start +1;
            HashSet<Character> hash = new HashSet<>();
            hash.add(s.charAt(start));
            while(end < s.length() && !hash.contains(s.charAt(end))){
                hash.add(s.charAt(end));
                end++;
            }
            start++;
            len = hash.size() > len? hash.size():len;
        }
        return len;
    }

30. 串联所有单词的子串

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
这题是一个开始位置就一个窗口,没有收缩

 public List<Integer> findSubstring(String s, String[] words) {
        if(s==null||s.length()==0||words.length==0) return new ArrayList<Integer>();
        List<Integer> res = new ArrayList<>();
        HashMap<String, Integer> dict = new HashMap<>();
        for(int i = 0; i< words.length;i++){
            int num = dict.getOrDefault(words[i],0);
            dict.put(words[i],num+1);
        }
        int len = words[0].length();
        int numW = words.length;
        int start = 0;
        int required = dict.size();
        while(start < s.length()- numW*len+1){
            HashMap<String, Integer> window = new HashMap<>();
            //因为单词是固定长度的,所以每次取固定长度的子串作为单词考虑
            int count = 0;
            while(count < numW){
              String word = s.substring(start+ count*len,start+(count+1)*len);
              if(dict.containsKey(word)){
                  window.put(word,window.getOrDefault(word,0)+1);
                  if(window.get(word) > dict.get(word)){
                      break;
                  }
                  count++;
              }else{
                  break;
              }
            }
            if(count == numW){
                res.add(start);
            }
             start++;
        }

        return res;
    }
}

76. 最小覆盖子串⭐️

这题是只有一个窗口,夸张和收缩
还是滑动窗口,但是有窗口的扩和缩。跟上面的438很像

 public String minWindow(String s, String t) {
        if(s==null ||s.length() < t.length()) return "";
        Map<Character,Integer> dictT = new HashMap<>();
        for(int i = 0;i<t.length();i++){
            int num = dictT.getOrDefault(t.charAt(i),0);
            dictT.put(t.charAt(i), num+1);
        }
        int start = 0, end = 0;
        int required = dictT.size();//有多少个不同的字符
        int formed = 0; //已经满足要求的字符的个数
        int[] res = {-1,0,0};
        Map<Character,Integer> cur = new HashMap<>();//存的是当前窗口各个字符及其数目
        while(end<s.length()){
            char c = s.charAt(end);
            int n = cur.getOrDefault(c,0);
            cur.put(c,n+1);
            //这里有个integer的点,超过-128到127的话,integer的值是以对象的形式存在堆中的,所以不能直接比较
            if(dictT.containsKey(c) && dictT.get(c).intValue() == cur.get(c).intValue()) formed++;
            //缩
            while(formed == required && start<=end) {
                c = s.charAt(start);
                //更新
                if(res[0] == -1 || end-start < res[2]-res[1]){
                    res[0] = end-start+1;
                    res[1] = start;
                    res[2] = end;
                }
                //更新当前窗口对应的map和formed
                cur.put(c,cur.get(c)-1);
                if(dictT.containsKey(c) && cur.get(c) <dictT.get(c)){
                    formed--;
                }
                start++;
            }
            //扩
            end++;          
        }
        return res[0]==-1? "":s.substring(res[1],res[2]+1);
    }

424. 替换后的最长重复字符

647. 回文子串 ⭐️

回文一定要注意,处理的时候,一定要分奇偶

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。

解法一:从回文的定义出发,找回文中心

 public int countSubstrings(String s) {
        if(s == null || s.equals("")){
            return 0;
        }
       int res = 0;
       int n = 2*s.length()-1;
       //错误做法,没考虑偶数的情况,空格也有可能称为回文中心
       for(int i = 0; i<n; i++){
           //先让left和right指向同一个,或相邻的
           int left = i/2;
           int right = left+i%2;
           while(left>=0 && right< s.length() && s.charAt(left)==s.charAt(right)){
              res++;
               left--;
               right++;
           }
       }
       return res;
    }

解法二: 详见 DP-字符串DP

public int countSubstrings(String s) {
       if(s==null||s.length()==0) return 0;
       boolean[][] dp = new boolean[s.length()][s.length()];
       //初始化
       for(int i =0;i<s.length();i++){
           dp[i][i] = true;
       }
       int res = s.length();
       //dp,只需要填上三角矩阵即可
       for(int i = s.length()-1;i>=0;i--){
           for(int j = i+1;j<s.length();j++){
               if(s.charAt(i)==s.charAt(j)){
                   if(j-i == 1) dp[i][j] = true;//偶数的情况
                   else dp[i][j] = dp[i+1][j-1];
                   }else{
                       dp[i][j] =false;
                   }
                    if(dp[i][j]) res++;
               }
           }
        return res;
    }

5 最长回文子串

public String longestPalindrome(String s) {
        if(s==null||s.length()==0) return s;
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        int[] res = {-1,0,0};
        for(int i = 0;i<n;i++) dp[i][i] = true;

        for(int i=n-1;i>=0;i--){
            for(int j= i+1;j<n;j++){
                if(s.charAt(i)==s.charAt(j)){
                    if(j-i==1) dp[i][j] = true;
                    else dp[i][j] = dp[i+1][j-1];
                }else{
                    dp[i][j] = false;
                }
                if(dp[i][j]==true && j-i+1 > res[0]){
                    res[0] = j-i+1;
                    res[1] = i;
                    res[2] = j;
                }
            }
        }
        return s.substring(res[1],res[2]+1);
    }

最长回文串

在代码中,我们用 ans 存储回文串的长度,由于在遍历字符时,ans 每次会增加 v / 2 * 2,因此 ans 一直为偶数。但在发现了第一个出现次数为奇数的字符后,我们将 ans 增加 1,这样 ans 变为奇数,在后面发现其它出现奇数次的字符时,我们就不改变 ans 的值了。

class Solution {
    public int longestPalindrome(String s) {
        int[] count = new int[128];
        int length = s.length();
        for (int i = 0; i < length; ++i) {
            char c = s.charAt(i);
            count[c]++;
        }

        int ans = 0;
        for (int v: count) {
            ans += v / 2 * 2;
            if (v % 2 == 1 && ans % 2 == 0) {
                ans++;
            }
        }
        return ans;
    }
}

子序列

1143最长公共子序列 (LCS) ⭐️

 public int longestCommonSubsequence(String text1, String text2) {
        if(text1==null||text2==null||text1.length()==0||text2.length()==0) return 0;
        int len1= text1.length();
        int len2 = text2.length();
        if(len2> len1) return longestCommonSubsequence(text2,text1);
        int[][] dp = new int [len2][len1];
        //初始化
        if(text1.charAt(0)==text2.charAt(0)) dp[0][0] = 1;
        else dp[0][0] = 0;
        //第一行
        for(int i = 1;i<len1;i++){
            if(text1.charAt(i) == text2.charAt(0)) dp[0][i] = 1;
            else dp[0][i] = dp[0][i-1];
        }
        //第一列
        for(int j = 1;j< len2;j++){
            if(text1.charAt(0) == text2.charAt(j)) dp[j][0] = 1;
            else dp[j][0] = dp[j-1][0];
        }
        //dp
        for(int i = 1;i<len2;i++){
            for(int j = 1;j<len1;j++){
                if(text1.charAt(j)==text2.charAt(i)){
                    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[len2-1][len1-1];
        }

300最长上升子序列 (LIS) ⭐️

 public int lengthOfLIS(int[] nums) {
        if(nums.length== 0) return 0;
        //状态:以i为结尾的子序列的长度
        int n = nums.length;
        int[] dp = new int [n];
        //初始化
        Arrays.fill(dp,1);
        for(int i = 1;i<n;i++){
            for(int j= 0;j<i;j++){
                if(nums[i]> nums[j]){
                   dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
        }
         int res = 0;
         for(int i =0;i<n;i++){
             res = Math.max(res,dp[i]);
    }
    return res;
    }

516 最长回文子序列

 public int longestPalindromeSubseq(String s) {
        if(s==null || s.length() ==0) return 0;
        int n = s.length();
        //从i到j位置的子序列长度
        int[][] dp = new int[n][n];
        //初始化
        for(int i = 0;i<n;i++){
            dp[i][i] = 1;
        }
        //dp
        for(int i = n-1;i>=0;i--){
            for(int j = i+1;j<n;j++){
                if(s.charAt(i)==s.charAt(j)){
                    dp[i][j] = dp[i+1][j-1]+2;
                }else{
                    dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]);
                }
            }
        }
        return dp[0][n-1];
    }

最短包含子串

import java.io.*;
import java.util.*;
public class Main{
    public static void main(String[] args)throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s1 = br.readLine();
        String s2 = br.readLine();
        int res = getRes(s1,s2);
        System.out.println(res);
    }
    public static int getRes(String s1,String s2){
        if(s1==null&&s2==null) return 0;
        if(s1==null||s2==null) return 0;
        if(s1.length()==0||s2.length()==0||s1.length()<s2.length()) return 0;
        char[] arr = s1.toCharArray();
        char[] p = s2.toCharArray();
        int[] map = new int[256];
        for(int i=0;i<p.length;i++){
            map[p[i]]++;            
        }
        int left = 0;
        int right = 0;
        int minLen = Integer.MAX_VALUE;
        int match = p.length;
        while(right!=arr.length){
            map[arr[right]]--;
            if(map[arr[right]]>=0){
                match--;
            }
            if(match==0){
                while(map[arr[left]]<0){
                    map[arr[left]]++;
                    left++;
                }
                minLen = Math.min(right-left+1,minLen);
                match++;
                map[arr[left++]]++;
            }
            right++;
        }
        return minLen==Integer.MAX_VALUE?0:minLen;
    }
}

括号匹配套题

//这题属于匹配括号的类型

class Solution {
    //栈
    //左括号就入栈,右括号就要出栈看是不是对应的左(怎么看是不是对应的左?用个map)
    public boolean isValid(String s) {
        if(s==null) return true;
        int index = 0;
        LinkedList<Character> dict = new LinkedList<>();
        HashMap<Character,Character> map = new HashMap<>(){{
            put(')','(');
            put(']','[');
            put('}','{');
        }};
        while(index < s.length()){
            char c = s.charAt(index);
            if(c=='(' || c=='{' || c=='[') dict.add(c);
            else{//如果按这种方法写,挨个匹配左右是不是很烦,怎么可以直接确定它们的对应关系
                if(dict.size()==0) return false;
                char cc = dict.removeLast();
                if(cc != map.get(c)) return false;
            }
            index++;
        }
        return dict.size()==0? true: false;
    }
}

//这里只有一种 括号 ,在每一次只能有两种选择。,只需要控制右的数量不大于左,左的数量不大于n

22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

class Solution {
      List<String> res = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        StringBuilder s = new StringBuilder();
       dfs(s,0,0,n);
       return res;
    }
    public void dfs(StringBuilder cur, int left, int right, int n){
        if(right >left || left > n){
            return ;
        }
        if(cur.length() == 2*n){
            res.add(cur.toString());
            return  ;
        }
        //像搜索地图可以上下左右,这里只能两种
        cur.append("(");
        dfs(cur,left+1,right,n);
        cur.deleteCharAt(cur.length()-1);
        cur.append(")");
        dfs(cur,left,right+1, n);
        cur.deleteCharAt(cur.length()-1);
    }   
}

32 最长有效括号


class Solution {
    //用栈的思想
    //有栈是不是就可以递归
    //递归的 优化 栈
    public int longestValidParentheses(String s) {
        if(s==null||s.length()==0) return 0;
        int max = 0;
        int[] dp = new int[s.length()+1];
      
        s = " "+s;
        for(int i =2; i< s.length(); i++){
           if(s.charAt(i) ==')'){
               if(s.charAt(i-1) == '('){
                   dp[i] = dp[i-2] +2;
               }else if (s.charAt(i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2];
                }
                max = Math.max(max, dp[i]);
           }
            }
            return max;
    }
}

栈、队列

两个栈实现一个队列

import java.util.Stack;

/*
 * 使用两个栈实现一个队列
 */
public class StackToQueue {
	private Stack<Integer> stackPush;
	private Stack<Integer> stackPoll;
	
	//构造,初始化栈
	public StackToQueue() {
		stackPush = new Stack<Integer>();
		stackPoll = new Stack<Integer>();
	}
	/*
	 * 因为是一次性把push的元素全倒在poll里面
	 * 这里要重点理解一下!!!!!!!!!!!!!!!!!!
	 */
	public void push(int num)
	{
		stackPush.push(num);
	}
	public int poll() {
		if(stackPoll.empty() && stackPush.empty()) {
			throw new RuntimeException("Queue is empety");
		}
		else if(stackPoll.empty()) {
			while(!stackPush.empty()) {
				stackPoll.push(stackPush.pop());
			}
		}
		return stackPoll.pop();
	}
	public int peek() {
		if(stackPush.empty() && stackPoll.empty()) {
			throw new RuntimeException("Queue is empety");
		}else if(stackPoll.empty()) {
			while(!stackPush.empty()) {
				stackPoll.push(stackPush.pop());
			}
		}
		return stackPoll.peek();
	}
	

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		StackToQueue sq = new StackToQueue();
		sq.push(5);
		
	    Integer num = sq.peek();
	    System.out.println(num);
	}

两个队列实现一个栈

有点像两个杯子挪乒乓球,每次都只留杯子底的那个,把前面的倒进另一个杯子。

public class QueueToStack {
	private Queue<Integer> data;
	private Queue<Integer> help;
	 
	public QueueToStack() {
		data = new LinkedList<Integer>();
		help = new LinkedList<Integer>();
	}
   public void push(int num) {
	   data.add(num);
   }
   //integer ? int
   public int pop() {
	   if(data.isEmpty()) {
		   throw new RuntimeException("stack is empety");
	   }while(data.size()>1) {
		   help.add(data.poll());
	   }
	   int num = data.poll();
	   swap();
	   return num;
   }
   public int peek() {
	   if(data.isEmpty()) {
		   throw new RuntimeException("stack is empety");
	   }while(data.size()>1) {
		   help.add(data.poll());
	   }
	   int num = data.poll();
	   help.add(num);
	   swap();
	   return num;
   }
   private void swap() {
       Queue<Integer> tmp = help;
       help = data;
       data = tmp;
   }
   }

单调栈

84 柱状图的最大矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。
写了八百年一直错错错,加个哨兵就那么简单!!!!!!!!!!!!!!!!!!!!!

public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        if (len == 0) {
            return 0;
        }

        if (len == 1) {
            return heights[0];
        }

        int res = 0;
        //加两个 哨兵
        int[] newHeights = new int[len + 2];
        newHeights[0] = 0;
        System.arraycopy(heights, 0, newHeights, 1, len);
        newHeights[len + 1] = 0;
        len += 2;
        heights = newHeights;

        Deque<Integer> stack = new ArrayDeque<>(len);
        // 先放入哨兵,在循环里就不用做非空判断
        stack.addLast(0);
        
        for (int i = 1; i < len; i++) {
            while (heights[i] < heights[stack.peekLast()]) {
                int curHeight = heights[stack.pollLast()];
                int curWidth = i - stack.peekLast() - 1;
                res = Math.max(res, curHeight * curWidth);
            }
            stack.addLast(i);
        }
        return res;

    }

你可能感兴趣的:(Leetcode)