代码随想录

数组

二分查找

1.二分查找

思路:使用双指针,一个指向开头,一个指向末尾,然后求和除以2的阿斗平均数mid,然后和target比较,如果相等直接返回下标mid,target大,就l = mid+1,target小r = mid-1

class Solution {
    public int search(int[] nums, int target) {
        int l = 0,r = nums.length-1;
        while( l <= r){
            int mid = (l+r)/2;
            if(target == nums[mid]) return mid;
            else if(target > nums[mid]) l = mid+1;
            else r = mid-1;
        }
        return -1;
    }
}

2.搜索插入位置

思路:使用二分查找,遍历到结束,左指针l的位置就是插入的位置。

class Solution {
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;
        int l = 0,r = n-1;
        while(l <= r){
            int mid = l+ (r-l)/2;
            if(nums[mid] >= target) r = mid-1;
            else l = mid+1;
        }
        return l;
    }
}

3.在排序数组中查找元素的第一个和最后一个位置

思路:先初始化数组res为[-1,-1],先使用二分查找找到target,不存在的话返回数组res。找到的话设置双指针,一个向后一个向前,遍历到不等于target的位置,然后记录其下标,然后把下标写入res中.

class Solution {
    public int[] searchRange(int[] nums, int target) {
       int[] res = new int[2];
       res[0] = -1;
       res[1] = -1;
       int l = 0;
       int r = nums.length-1;
       while(l <= r){
            int mid = l + (r-l)/2;
            if(nums[mid] == target){
                int left = mid;
                int right = mid;
                while(left>=0 && nums[left] == target)left--;
                while(right < nums.length && nums[right] == target)right++;
                res[0] = left+1;
                res[1] = right-1;
                break;
            }else if(nums[mid] > target) r = mid-1;
            else l = mid+1;
       }
       return res;
    }
}

4.x的平方根

思路:因为是求的整数,因此这个数一定是在0,x这个范围内的,因此可以使用二分查找,得到mid,计算mid*mid和x比较大小,如果小雨等于x小就令l = mid+1,同时把值赋给ans作为最终答案,比x大就令r=mid-1。最终遍历结束的位置会是这个平方根的整数。

class Solution {
    public int mySqrt(int x) {
        int l = 0,r = x,ans = 0;
        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;
    }
}

5.有效的完全平方数

思路:使用二分查找,找到其mid*mid的值刚好等num就直接返回true,遍历结束没返回true说明没找到,返回false

class Solution {
    public boolean isPerfectSquare(int num) {
        int l = 0,r = num,ans = 0;
        while(l <= r){
            int mid = l + (r-l)/2;
            if((long)mid*mid == num){
                return true;
            }else if((long)mid*mid > num) r = mid-1;
            else l = mid+1;
        }
        return false;
    }
}

移除元素

6.移除元素

思路:使用一个指针来表示删除元素之后的位置,遍历数组,令nums[k++] = nums[i],当遇到值等于val的时候直接跳过;最后遍历结束后k的大小就是最后删除元素之后的长度。

class Solution {
    public int removeElement(int[] nums, int val) {
        int k = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i] == val)continue;
            nums[k++] = nums[i];
        }
        return k;
    }
}

7.删除排序数组中的重复项

思路:也是一样创建一个k指针表示删除之后的数组位置,遍历数组,当i==0或者当前数值和前一个相等时,nums[k++] = nums[i],遍历结束后返回k

class Solution {
    public int removeDuplicates(int[] nums) {
        int k = 0;
        for(int i=0;i<nums.length;i++){
            if(i == 0 || nums[i] != nums[i-1])nums[k++] = nums[i];
        }
        return k;
    }
}

8.移动零

思路:设置一个指针k表示移动之后的数组位置,当遇到值等于0的时候直接跳过,否则就是nums[k++] = nums[i]遍历结束之后把后面都补上0即可

class Solution {
    public void moveZeroes(int[] nums) {
        int k = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i] == 0)continue;
            nums[k++] = nums[i];
        }
        for(;k<nums.length;k++){
            nums[k] = 0;
        }
    }
}

9.比较含退格的字符串

思路:构建一个函数用来得到计算之后字符串,然后直接比较计算之后是否相等即可。具体逻辑是创建一个StrungBuffer然后遍历字符串每个字符,是字符就直接append到里面,如果是'#'就删除最后一个字符,然后遍历结束后返回并转换回String。

class Solution {
    public boolean backspaceCompare(String s, String t) {
        return build(s).equals(build(t));
    }
    public String build(String s){
        StringBuffer res = new StringBuffer();
        for(int i=0;i<s.length();i++){
            char ch = s.charAt(i);
            if(ch!= '#') res.append(ch);
            else {
                if(res.length() > 0) res.deleteCharAt(res.length()-1);
            }
        }
        return res.toString();
    }
}

10.有序数组的平方

思路:设置双指针,因为计算的是平方,因此负数的平方之后值可能会变大。然后创建一个新的数组用来存放平方之后的值。每次将左右指针的值平方,然后比较大小,大的先放入数组,然后移动指针,直到遍历结束,然后返回。

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int l = 0;
        int r = n-1;
        int[] res = new int[n];
        for(int i=n-1;i>=0;i--){
            int nums1 = nums[l]*nums[l];
            int nums2 = nums[r]*nums[r];
            if(nums1>nums2){
                res[i] = nums1;
                l++;
            }else{
                res[i] = nums2;
                r--;
            }
        }
        return res;
    }
}

有序数组的平方

11.有序数组的平方

思路:因为有负数,因此平方后的数也可能是负数这边大,只用双指针的思路。创建一个新的数组来存放平方后的值,一个指向开头,一个指向末尾,然后同时求两个值的平方,哪个大就先放入数组,然后指针移动到下一个。

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int l = 0;
        int r = nums.length-1;
        int[] res = new int[n];
        int k = n-1;
        while(l <= r){
            int num1 = nums[l]*nums[l];
            int num2 = nums[r]*nums[r];
            if(num1 > num2){
                res[k--] = num1;
                l++;
            }else{
                res[k--] = num2;
                r--;
            }
        }
        return res;
    }
}

长度最小的子数组

12.长度最小的子数组

思路:使用双指针+滑动窗口的思路,一个指针遍历数组,一个指针代表滑动窗口的左边界,先遍历数组,然后创建一个sum表示求和值,一直累加,创建一个len表示最小长度,默认为最大值。当sum>=target时,就计算长度,end-start+1和len比较,然后更新左边界start++,然后sum的值扣减移除出去的那个数值,然后继续遍历直到不满足sum>=target为止。最后返回时要注意len是否还等于Integer.MAX_VALUE等于的话说明找不到子数组成立的,那就直接返回0.

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int n = nums.length;
        int start = 0;
        int len = Integer.MAX_VALUE;
        int sum = 0;
        for(int end = 0;end < n;end++){
            sum += nums[end];
            while(sum >= target){
                len = Math.min(len,end-start+1);
                sum-=nums[start];
                start++;
            }
        }
        return len==Integer.MAX_VALUE?0:len;
    }
}

13.水果成篮

思路:

我们可以使用滑动窗口解决本题,left 和 right 分别表示满足要求的窗口的左右边界,同时我们使用哈希表存储这个窗口内的数以及出现的次数。

我们每次将 right 移动一个位置,并将 fruits[right] 加入哈希表。如果此时哈希表不满足要求(即哈希表中出现超过两个键值对),那么我们需要不断移动 left,并将 fruits[left] 从哈希表中移除,直到哈希表满足要求为止。

需要注意的是,将 fruits[left]从哈希表中移除后,如果 fruits[left] 在哈希表中的出现次数减少为 0,需要将对应的键值对从哈希表中移除。

class Solution {
    public int totalFruit(int[] fruits) {
        int left = 0,right = 0;
        int n = fruits.length;
        Map<Integer,Integer> map = new HashMap<>();
        int res = 0;
        for(;right<n;right++){
            map.put(fruits[right],map.getOrDefault(fruits[right],0)+1);
            while(map.size() > 2){
                map.put(fruits[left],map.get(fruits[left])-1);
                if(map.get(fruits[left]) == 0) map.remove(fruits[left]);
                left++;
            }
            res = Math.max(res, right - left +1);
        }
        return res;
    }
}

14.最小覆盖子串

思路:使用滑动窗口的方法来解决这个问题:

(1)初始化两个哈希表一个need,一个window,need用来记录t字符串的字符和数量,window用来记录滑动窗口里面的字符和数量

(2)创建left和right作为双指针,初始化为0

(3)扩展窗口,right++,把遍历到的字符加入到window的哈希表内,然后创建一个valid用来记录加入的字符是need里面存在的字符的数量,只有当字符串的数量也相等时valid才会++,然后检查valid是否等于t字符串的长度,相等的话说明滑动窗口目前已包含t中所有的字符了

(4)然后记录一下滑动窗口的左边界和窗口长度,再开始收缩窗口直到不满足相等为止

(5)然后计算一下窗口的长度,如果更小了,就更新结果,用start记录一下窗口的起始位置

class Solution {
    public String minWindow(String s, String t) {
         if(s == null || t == null || s.length() == 0 || t.length() == 0 || s.length() < t.length())return "";
         Map<Character,Integer> need = new HashMap<>();
         for(int i=0;i<t.length();i++) need.put(t.charAt(i),need.getOrDefault(t.charAt(i),0)+1);
         Map<Character,Integer> window = new HashMap<>();
         int start = 0,end = 0,valid =0,len = Integer.MAX_VALUE,left =0;
         while(end < s.length()){
            char c = s.charAt(end);
            end++;
            if(need.containsKey(c)){
                window.put(c,window.getOrDefault(c,0)+1);
                if(need.get(c) == window.get(c))
                    valid++;
            }
            while(valid == t.length()){
                if(end - left < len){
                    start = left;
                    len = end-left;
                }
                char d = s.charAt(left);
                left++;
                if(need.containsKey(d)){
                    if(need.get(d) == window.get(d)){
                        valid--;
                    }
                    window.put(d,window.get(d)-1);
                }
            }
         }
         return len==Integer.MAX_VALUE?"":s.substring(start,start+len);
    }
}

螺旋矩阵

15.生成螺旋矩阵

思路:使用模拟的方法,创建四个指针表示top,down,left,right,从第一行开始遍历,创建一个赋值变量count = 1,把值填入遍历到的数组下标的值,然后先遍历上边界,然后遍历有边界,然后遍历下边界,然后遍历左边界,遍历完边界的时候,让其指针向内移动一格,然后遍历结束后返回数组。

class Solution {
    public int[][] generateMatrix(int n) {
        int[][] res = new int[n][n];
        int left = 0,right = n-1;
        int bottom = n-1, top = 0;
        int count = 1;
        while(count <= n*n){
            for(int i = left;i<=right;i++) res[top][i] = count++;
            top++;
            for(int i = top;i<=bottom;i++) res[i][right] = count++;
            right--;
            for(int i = right;i>=left;i--) res[bottom][i] = count++;
            bottom--;
            for(int i = bottom;i>=top;i--) res[i][left] = count++;
            left++;
        }
        return res;
    }
}

16.遍历二维螺旋矩阵

思路:遍历的二位矩阵不一定是正方形,因此不能使用上面生成螺旋矩阵的模拟思路,要在此基础上添加一个判断,就是加入++l>r,++u>d,--r<l,--d<u就需要直接停止遍历,这样输出的才是正常的输出。其他的部分和上面生成差不多。主要就是使用一个list表用来存储遍历到的值,最后返回即可

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<>();
        if(matrix.length == 0)return res;
        int n = matrix.length,m = matrix[0].length;
        int l = 0, r = m-1;
        int u = 0,d = n-1;
        while(true){
            for(int i=l;i<=r;i++) res.add(matrix[u][i]);
            if(++u>d)break;
            for(int i=u;i<=d;i++) res.add(matrix[i][r]);
            if(--r<l)break;
            for(int i=r;i>=l;i--) res.add(matrix[d][i]);
            if(--d<u)break;
            for(int i=d;i>=u;i--) res.add(matrix[i][l]);
            if(++l>r)break;
        }
        return res;
    }
}

17.遍历二维螺旋矩阵

思路,和上面一样,只是这个使用数组返回,只需要创建一个数组长度是二位矩阵长宽相乘,然后再使用一个count用来记录这个数组的下标,然后模拟遍历,把数值赋值进去即可。

class Solution {
    public int[] spiralArray(int[][] array) {       
        if(array.length == 0)return new int[0];
        int n = array.length,m = array[0].length;
        int[] res = new int[n*m];
        int l = 0, r = m-1;
        int u = 0,d = n-1;
        int count = 0;
        while(true){
            for(int i=l;i<=r;i++) res[count++] = array[u][i];
            if(++u>d)break;
            for(int i=u;i<=d;i++) res[count++] = array[i][r];
            if(--r<l)break;
            for(int i=r;i>=l;i--) res[count++] = array[d][i];
            if(--d<u)break;
            for(int i=d;i>=u;i--) res[count++] = array[i][l];
            if(++l>r)break;
        }
        return res;
    }
}

链表

18.移除链表元素

思路:设置一个节点sen,该节点的next指向head,设置两个指针,一个pre=sen,一个node = head,然后node遍历链表,如果node.val = val就移除该节点node = node.next;pre.next = node;

遍历结束后判断一种特殊情况,当node == head时,head = head.next;pre.next = head;

最后返回sen.next;

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null)return null;
        ListNode sen = new ListNode();
        sen.next = head;
        ListNode pre = sen;
        ListNode node = head;
        while(node!=null){
            if(node.val == val){
                node = node.next;
                pre.next = node;
            }else{
                pre = node;
                node = node.next;
            }
        }
        if(node == head){
            head = head.next;
            pre.next = head;
        }
        return sen.next;
    }
}

19.设计链表

class ListNode{
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) {
        this.val = val;
    }
}
class MyLinkedList {
    int size;
    ListNode head;
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    
    public int get(int index) {
        if(index < 0 || index >= size) return -1;
        ListNode node = head;
        for(int i=0;i<=index;i++) node = node.next;
        return node.val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        if(index > size) return ;
        if(index < 0)index = 0;
        size++;
        ListNode node = head;
        for(int i=0;i<index;i++){
            node = node.next;
        }
        ListNode nodenext = new ListNode(val);
        nodenext.next = node.next;
        node.next = nodenext;
    }
    
    public void deleteAtIndex(int index) {
        if(index < 0 || index>=size) return ;
        size--;
        ListNode node = head;
        for(int i=0;i<index;i++) node = node.next;
        node.next = node.next.next;
    }
}

20.反转链表

思路:设置两个指针,一个pre指向null,一个node指向head,遍历node,先创建一个next节点记录node.next的位置,然后把node.next指向pre,然后pre = node,node = next。最后返回pre

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode node = head;
        while(node!=null){
            ListNode next = node.next;
            node.next = pre;
            pre = node;
            node = next;
        }
        return pre;
    }
}

21.两两交换链表中的节点

思路:先创建一个节点sen,sen.next = head;然后创建两个指针,一个pre = sen,一个node = head;然后node遍历链表,while(node.next!=null)。循环内部先创建一个next指针指向node.next的位置,然后进行交换,令node.next=next.next;next.next=node;pre.next=next。如果下两个节点不为空就可以继续循环if(node.next!=null),先将指针移动,pre=node,node=node.next。遍历结束后返回sen.next

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null)return head; 
        ListNode sen = new ListNode();
        sen.next = head;
        ListNode pre = sen;
        ListNode node = head;
        while(node.next!=null){
            ListNode next = node.next;
            node.next = next.next;
            next.next = node;
            pre.next = next;
            if(node.next!=null){
                pre = node;
                node = node.next;
            }
        }
        return sen.next;
    }
}

22.删除链表的倒数第n个节点

思路:创建一个节点sen,该节点的next指针指向head节点,然后创建两个指针,pre和node,pre指向sen,node指向head然后while遍历链表n--先让node先走n步,那么之后再继续遍历pre和node一起向后遍历,直到node==null这时,pre.next就是要删除的倒数第n个节点。然后先判断这个节点是否为head,head的话就直接head = head.next,不是的话就pre.next = pre.next.next,最后返回sen.next;

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode node = head;
        ListNode sen = new ListNode();
        sen.next = head;
        ListNode pre = sen;
        while(n>0){
            node = node.next;
            n--;
        }
        while(node!=null){
            node = node.next;
            pre = pre.next;
        }
        if(pre.next == head) head = head.next;
        pre.next = pre.next.next;
        return sen.next;
    }
}

23.链表相交

思路:使用双指针,一个是headA+headB,一个是headB+headA。两个组合的链表最终长度一致,因此遍历到结尾时一定是相交的位置。两个一起遍历while(a!=b)然后如果一个指针空了就接入另一个链表的head,然后直到二者相等。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode a = headA;
        ListNode b = headB;
        while(a!=b){
            a = a==null?headB:a.next;
            b = b==null?headA:b.next;
        }
        return a;
    }
}

24.环形链表

思路:双指针,快慢指针,fast每次走两步,slow每次走一步,遍历如果fast ==null说明没有环return null,如果slow = fast说明有环,然后领slow = head,继续遍历,while(slow!=fast)这时fast和slow都只走一步,当他们相等时,这个位置就是入口,具体原理是数学推导.

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null){
            slow = slow.next;
            fast = fast.next;
            if(fast==null)return null;
            else fast = fast.next;
            if(fast == slow){
                slow = head;
                while(slow!=fast){
                    fast = fast.next;
                    slow = slow.next;
                }
                return slow;
            }
        }
        return null;
    }
}

哈希表

25.有效的字母异位词

思路:两种方法,第一种是直接把两个字符串转化为字符数组,然后进行排序,然后对比排序后的两个数组是否相等。第二种是使用哈希表,用key-value的方法记录字符以及其出现的次数,然后先遍历字符串s,得到map,然后遍历字符串t,把t中出现的字符在map中减去,如果出现不存在或者要减去时数量已经等于0说明不匹配,直接返回false,遍历结束都没有返回false说明匹配返回true;

class Solution {
    public boolean isAnagram(String s, String t) {
        char[] c = s.toCharArray();
        char[] b = t.toCharArray();
        Arrays.sort(c);
        Arrays.sort(b);
        return Arrays.equals(b,c);
    }
}
class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length() != t.length())return false;
        Map<Character,Integer> map = new HashMap<>();
        for(int i=0;i<s.length();i++){
            char a = s.charAt(i);
            map.put(a , map.getOrDefault(a,0)+1); 
        }
        for(int i=0;i<t.length();i++){
            char a = t.charAt(i);
            if(!map.containsKey(a) || map.get(a) == 0)return false;
            map.put(a , map.get(a)-1); 
        }
        return true;
    }
}

26.两个数组的交集

思路:使用两个哈希表,一个把其中一个数组中的元素全部装进去,然后遍历另一个数组,如果遇到元素在表中存在,就加入到结果哈希表中,然后把哈希表转换为数组返回。

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> set = new HashSet<>();
        Set<Integer> res = new HashSet<>();
        for(int i=0;i<nums1.length;i++) set.add(nums1[i]);
        for(int i=0;i<nums2.length;i++){
            if(set.contains(nums2[i])){
                res.add(nums2[i]);
            }
        }
        int[] num = new int[res.size()];
        int i = 0;
        for(int n : res) num[i++] = n;
        return num;
    }
}

27.快乐数

思路:主要问题是循环计算,遍历计算的时候可能会有环,因此需要使用一个哈希表来记录计算出来过的数据,当计算后出现重复的直接返回false,如果遍历结束都没有说明是快乐数,返回true

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> set = new HashSet<>();
        while(n > 1){
            set.add(n);
            n = findpath(n);
            if(set.contains(n))return false;
        }
        return true;
    }
    public int findpath(int n ){
        int res = 0;
        while(n > 0){
            res += (n%10)*(n%10);
            n = n/10;
        }
        return res;
    }
}

28.两数之和

思路:(1)设计一个哈希表记录,遍历的数据元素和其下标,然后遍历到当前元素时,计算num = target - nums[i],查看哈希表内有没有等于num的元素,有的话就找到了一个组合之和为target,然后加入到res数组中。每次遍历的末尾吧遍历过的数据添加到哈希表中,这样可以保证不会遍历到重复的数据。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        int[] res = new int[2];
        int n = nums.length;

        for(int i=0;i<n;i++){
            int num = target - nums[i];
            if(map.containsKey(num)){
                res[0] = i;
                res[1] = map.get(num);
            }
            map.put(nums[i],i);
        }
        return res;
    }
}

(2)也是使用哈希表,不过先遍历一遍,把元素都放入表中,然后再遍历一次,也是计算num = target - nums[i]如果表中存在,且其下标不等于当前的下标就加入到res数组中。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        int[] res = new int[2];
        for(int i=0;i<nums.length;i++) map.put(nums[i],i);
        for(int i=0;i<nums.length;i++){
            int num = target - nums[i];
            if(map.containsKey(num)){
                if(map.get(num) == i)continue;
                res[0] = i;
                res[1] = map.get(num);
            }
        }
        return res;
    }
}

29.四数相加||

思路:使用哈希表先存储前两个数组的加和值和值的数量,然后再两层遍历后两个数组,也是先求出后两个数组元素的加和值,然后在哈希表内搜索-sum的数量,加入到最终的res计数中。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer,Integer> map = new HashMap<>();
        int res = 0;
        for(int num1 : nums1){
            for(int num2 : nums2){
                int sum = num1 + num2;
                map.put(sum , map.getOrDefault(sum,0)+1);
            }
        }
        for(int num3 : nums3){
            for(int num4 : nums4){
                int sum = num3+num4;
                res += map.getOrDefault(-sum,0);
            }
        }
        return res;
    }
}

30.赎金信

思路:创建一个哈希表来记录magzine字符串里面的元素和数量,然后遍历ransomNote里面的字符,如果在哈希表中找不到或者数量已经为0说不能,返回false;否则就先让数量减1,然后再继续遍历,遍历结束如果都没有返回false,说明可以,就返回true

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        Map<Character,Integer> map = new HashMap<>();
        for(int i=0;i<magazine.length();i++) {
            char c = magazine.charAt(i);
            map.put(c,map.getOrDefault(c,0)+1);
        }
        for(int i=0;i<ransomNote.length();i++){
            char c = ransomNote.charAt(i);
            if(map.getOrDefault(c,0) == 0) return false;
            map.put(c,map.get(c)-1);
        }
        return true;
    }
}

31.三数之和

思路:先排序,然后遍历数组,创建双指针,如果加和等于0,就加入到最终的结果集中,然后移动左右指针,主要是去重,如果遇到相同的数值直接跳过。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
       int n = nums.length;
       List<List<Integer>> res = new ArrayList<>();
       Arrays.sort(nums);
       for(int i=0;i<n;i++){
            if(i>0 && nums[i] == nums[i-1])continue;
            int l = i+1,r = n-1;
            while(l < r){
                int sum = nums[i] + nums[l] + nums[r];
                if(sum == 0){
                    res.add(Arrays.asList(nums[i],nums[l],nums[r]));
                    while(l<r && nums[r]==nums[r-1])r--;
                    while(l<r && nums[l]==nums[l+1])l++;
                    r--;l++;
                }else if(sum > 0){
                    r--;
                }else l++;
            }
       }
       return res;
    }
}

32.四数之和

思路:双指针+遍历,先把nums进行sort排序,然后判断第一个数是不是大于0且大于target,如果是的话说明组不成直接返回空的res,然后两层遍历,同时要去重,第一层遍历第一个数,第二层遍历第二个数,然后创建两个指针l=j+1,r=n-1,然后计算sum是否等于target,是的话就是把这个组合加入到res中,然后移动两个是指针到新的位置,这里要去重。

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        if(nums[0] > 0 && nums[0] > target)return res;
        int n = nums.length;
        for(int i=0;i<n;i++){
            if(i>0 && nums[i] == nums[i-1])continue;
            for(int j=i+1;j<n;j++){
                if(j>i+1&&nums[j] == nums[j-1])continue;
                int l = j+1,r = n-1;
                while(l<r){
                    int sum = nums[i] + nums[j] + nums[l] + nums[r];
                    if(sum == target){
                        res.add(Arrays.asList(nums[i],nums[j],nums[l],nums[r]));
                        while(l<r && nums[r] == nums[r-1])r--;
                        while(l<r && nums[l] == nums[l+1])l++;
                        l++;r--;
                    } else if(sum > target) r--;
                    else l++;
                    
                }
            }
        }
        return res;
    }
}

字符串

33.反转字符串

class Solution {
    public void reverseString(char[] s) {
        int l = 0,r = s.length-1;
        while(l < r){
            char tmp = s[l];
            s[l] = s[r];
            s[r] = tmp;
            l++;r--;
        }
    }
}

34.反转字符串

思路:遍历数组,遍历的时每次移动i+2*k,然后int n = Math.min(i+k,s.length()-1);就是查看当前2k的前k个是否存在,取最小的,然后进行反转。

class Solution {
    public String reverseStr(String s, int k) {
       char[] res = s.toCharArray();
       for(int i=0;i<res.length;i+=2*k){
            int n = Math.min(i+k,s.length())-1;
            int j = i;
            while(j<n){
                char tmp = res[n];
                res[n--] = res[j];
                res[j++] = tmp;
            }
       }
       return String.valueOf(res);
    }

}

34.替换数字

直接把字符串转换为字符集合,然后遍历集合,创建一个StringBuffer来保存最终结果,如果遇到是数字就添加number字符到最终结果里,不是的话就正常添加,最后res。toString();

import java.util.*;
public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        String s = in.nextLine();
        char[] c = s.toCharArray();
        StringBuffer res = new StringBuffer();
        for(int i=0;i<c.length;i++){
            if(Character.isDigit(c[i])){
                res.append("number");
            }else{
                res.append(c[i]);
            }
        }
        System.out.println(res.toString());
        
    }
}

35.反转字符串中的单词

思路:根据java语言特性,例如trim来去除开头末尾的空格,split来进行字符分割,这里是根据俄空格进行分割使用split("\\s+"),\\s+ 是一个正则表达式,表示匹配一个或多个空白字符,包括空格、制表符、换行符等。在 Java 的字符串中,\ 是转义字符,因此需要使用两个反斜杠 \\ 来表示一个反斜杠。所以 \\s+ 表示匹配一个或多个空白字符。然后变成字符串数组,然后在进行反转操作,最后使用join把空格再补进去并拼接成字符串(String.join(" ",words))

class Solution {
    public String reverseWords(String s) {
       s = s.trim();
       String[] words = s.split("\\s+");
       reverse(words);
       return String.join(" ",words); 
    }
    public void reverse(String[] words){
        int l = 0,r = words.length-1;
        while(l < r){
            String tmp = words[l];
            words[l] = words[r];
            words[r] = tmp;
            l++;r--;
        }
    }
}

36.右旋字符串

思路:先把对右旋的数进行取摸,摸为字符串的长度,整个字符串反转,然后单独反转前k个字符,然后再单独反转k个之后的字符,然后就是右旋的效果

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        in.nextLine(); 
        String s = in.nextLine();
        char[] c = s.toCharArray();
        n = n % c.length;
        reverse(c,0,c.length-1);
        reverse(c,0,n-1);
        reverse(c,n,c.length-1);
        System.out.println(c);
        
    }
    public static void reverse(char[] c,int l,int r){
        while(l < r){
            char a = c[l];
            c[l] = c[r];
            c[r] = a;
            l++;r--;
        }
        
    }
    
}

37.找出字符串中第一个匹配项的下标

思路:遍历haystack的每个字符,把每个字符作为匹配的起始位置,然后一个个字符和needle中的进行对比,当对比完整个needle之后都匹配说明找到了,直接返回当前位置的下标。遍历结束没返回就是没找到,返回-1;

class Solution {
    public int strStr(String haystack, String needle) {
        int n = haystack.length(),m = needle.length();
        char[] a = haystack.toCharArray(),b = needle.toCharArray();
        for(int i=0;i<=n - m;i++){
            int j = i, k = 0;
            while(k < m && a[j] == b[k]){
                k++;
                j++;
            }
            if(k == m)return i;
        }
        return -1;
    }
}

38.重复的子字符串

思路:枚举。如果一个长度为 n 的字符串 s 可以由它的一个长度为 n′ 的子串 s′重复多次构成,那么:

(1)n一定是n‘的倍数

(2)s'一定是s的前缀

(3)对于任意i [n',n),右s[i] = s[i - n']

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int n = s.length();
        for (int i = 1; i * 2 <= n; ++i) {
            if (n % i == 0) {
                boolean match = true;
                for (int j = i; j < n; ++j) {
                    if (s.charAt(j) != s.charAt(j - i)) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    return true;
                }
            }
        }
        return false;
    }
}

双指针法

39.移除元素

思路:使用双指针,

思路:使用指针,i遍历数组,k遍历删除元素之后的位置,如果遍历到当前数组的值等于val,就跳过,否则nums[k++] = nums[i]。

class Solution {
    public int removeElement(int[] nums, int val) {
        int k = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i] == val) continue;
            else nums[k++] = nums[i];
        }
        return k;
    }
}

40.删除有序数组中的重复项

思路:也是同样的思路,一个遍历数组,一个遍历删除后的新位置,如果(i>0 && nums[i]==nums[i-1])就跳过,否则nums[k++] = nums[i];

class Solution {
    public int removeDuplicates(int[] nums) {
        int k = 0;
        for(int i=0;i<nums.length;i++){
            if(i>0 && nums[i]==nums[i-1]) continue;
            else nums[k++] = nums[i];
        }
        return k;
    }
}

41.移动零

思路:也是双指针,遍历数组,如果遇到零跳过,不是零就更新nums[k++] = nums[i],然后在遍历一次,从k开始,后面都补上0.

class Solution {
    public void moveZeroes(int[] nums) {
        int k = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]!=0)nums[k++] = nums[i];
        }
        for(int i = k;i<nums.length;i++){
            nums[k++] = 0;
        }
    }
}

42.比较含退格的字符串

思路:双指针: 遇到字母两指针都向前一位,遇到#号快指针向前一位,慢指针后退一位(注意0位置) 

class Solution {
    public boolean backspaceCompare(String s, String t) {
        char[] ss = s.toCharArray();
        char[] tt = t.toCharArray();

        return helper(ss).equals(helper(tt));
    }

    String helper(char[] c){
        int i = 0,j = 0;
        while(j < c.length){
            if(c[j] != '#'){ //遇到字母 两个指针都往前走
                c[i++] = c[j++];
            }else {
                j++;
                if(i > 0) i--;  // 遇到 #, i 指针向后退
            }
        }
        return new String(c).substring(0,i);
    }
}

43.有序数组的平方

思路:双指针,l和r分别从开头和末尾开始遍历,然后都平方,比较大小,大的放入新的数组中,移动指针

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        int l=0,r =n-1,k=0;
        for(int i=n-1;i>=0;i--){
            int num1 = nums[l]*nums[l];
            int num2 = nums[r]*nums[r];
            if(num1 > num2) {
                res[i] = num1;
                l++;
            }
            else{
                res[i] = num2;
                r--;
            } 
        }
        return res;
    }
}

44.长度最小的子数组

思路:使用双指针,一个遍历数组向后移动,一个表示子数组的左边界,创建一个sum表示目前加和,然后判断当前的sum是否大于等于target,满足后进入到while循环,先比较一下长度,len = Math.min(len,i-start+1),然后收缩左边界,知道不满足位置,最后返回len徐判断是否等于最大值,等于最大值说明没有组合符合要求。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int start = 0;
        int n = nums.length;
        int sum = 0;
        int len = Integer.MAX_VALUE;
        for(int i=0;i<n;i++){
            sum+=nums[i];
            while(sum >= target){
                len = Math.min(len,i-start+1);
                sum -= nums[start];
                start++;
            }
        }
        return len==Integer.MAX_VALUE?0:len;
    }
}

45.水果成篮

思路:使用双指针,使用一个哈希表来记录水果的数量和种类当种类超过2个时就进入while循环,收缩右边界,然后等到符合时退出,然后计算目前的长度。

class Solution {
    public int totalFruit(int[] fruits) {
        Map<Integer,Integer> map = new HashMap<>();
        int start = 0;
        int n = fruits.length;
        int len = 0;
        for(int i=0;i<n;i++){
            map.put(fruits[i],map.getOrDefault(fruits[i],0)+1);
            while(map.size()>2){
                map.put(fruits[start],map.getOrDefault(fruits[start],0)-1);
                if(map.get(fruits[start]) == 0) map.remove(fruits[start]);
                start++;
            }
            len = Math.max(len,i-start+1);
        }
        return len;
    }
}

46.最小覆盖子串

思路:双指针加滑动窗口,先用一个哈希表need记录t字符串所有的字符和数量,然后再用一个哈希表来记录s遍历的窗口字符和数量,现一直扩充有边界,然后吧字符加入到window窗口中,如果一个字符的数量满足就valid++,当valid==need.size()时,说明该窗口已经满足要求,然后进入while循环,先计算长度,如果比之前len小,就更新左边界start和长度len,然后将窗口向右收缩,最后遍历结束时,判断len是否等于最大值,等于的话说明找不到子串。

class Solution {
    public String minWindow(String s, String t) {
       Map<Character,Integer> need = new HashMap<>();
       Map<Character,Integer> window = new HashMap<>();
       int len = Integer.MAX_VALUE;
       int start = 0;
       int right = 0;
       int left = 0;
       int valid = 0;
       for(char c: t.toCharArray()) need.put(c,need.getOrDefault(c,0)+1);
       while(right < s.length()){
            char c = s.charAt(right);
            right++;
            if(need.containsKey(c)){
                window.put(c,window.getOrDefault(c,0)+1);
                if(window.get(c).equals(need.get(c))) valid++;
            }
            while(valid == need.size()){
                if(right - left < len){
                    len = right - left;
                    start = left; 
                }
                char d = s.charAt(left);
                left++;
                if(need.containsKey(d)){
                    if(window.get(d).equals(need.get(d))) valid--;
                    window.put(d,window.get(d)-1);
                }
            }
       }

       return len == Integer.MAX_VALUE ? "":s.substring(start,start+len);
    }
}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值