数组
二分查找
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);
}
}