电信保温杯笔记——代码随想录 刷题攻略 数组
电信保温杯笔记——代码随想录 刷题攻略
代码随想录 刷题攻略
电信保温杯笔记——代码随想录 刷题攻略
1.数组过于简单,但你该了解这些!
2.数组:每次遇到二分法,都是一看就会,一写就废
均使用左闭右开划分区间,只要是有序的结构,就可以用这个方法
704. 二分查找
class Solution {
public int search(int[] nums, int target) {
if (nums[0] > target || nums[nums.length -1] < target){
return -1;
}
int left = 0;
int right = nums.length;
while(left < right){
int mid = left + ((right - left ) >> 1);
if (nums[mid] > target){
right = mid;
}else if (nums[mid] < target){
left = mid +1;
}else{
return mid;
}
}
return -1;
}
}
35.搜索插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
if (nums[0] > target){
return 0;
}
if (nums[nums.length - 1] < target){
return nums.length;
}
int left = 0;
int right = nums.length;
while (left < right){
int mid = left + ((right - left) >> 1);
if (nums[mid] > target){
right = mid;
}else if (nums[mid] < target){
left = mid + 1;
}else {
return mid;
}
}
// left==right时,就是搜索的最后区间,最接近
// target的元素,target应该在它的左或右
return nums[left] > target ? left : (left + 1);
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length == 0){
return new int[]{-1, -1};
}
if ((nums[0] > target) || (nums[nums.length - 1] < target)){
return new int[]{-1, -1};
}
int left = 0;
int right = nums.length;
boolean found = false;
int mid = 0;
while(left < right){
mid = left + ((right - left) >> 1);
if (nums[mid] > target){
right = mid;
}else if (nums[mid] < target){
left = mid + 1;
}else{
found = true;
break;
}
}
// 找到插入的位置后,从该位置左右两个方向开始搜索
if (found == true){
left = mid;
right = mid;
for (int i = left - 1; i >= 0; i--){
if (nums[i] != target){
break;
}
left--;
}
for (int i = right + 1; i < nums.length; i++){
if (nums[i] != target){
break;
}
right++;
}
return new int[]{left, right};
}
return new int[]{-1, -1};
}
}
69.x 的平方根
class Solution {
public int mySqrt(int x) {
if (x == 0 || x == 1){
return x;
}
int left = 0;
int right = x;
while(left < right){
int mid = left + ((right - left) >> 1);
if (mid > x /mid){
right = mid;
}else if (mid < x /mid){
left = mid + 1;
}else{
return mid;
}
}
// left^2为最接近x的数
return left > x / left ? (left - 1) : left;
}
}
367.有效的完全平方数
class Solution {
public boolean isPerfectSquare(int num) {
if (num == 0 || num == 1){
return true;
}
// 这题不用long 或者long long 容易出错,
long left = 0;
long right = num + 1;
while (left < right){
long mid = left + ((right - left) >> 1);
if (mid * mid> num ){
right = mid;
// 下一行,如果不使用long,使用int,判断条件
// mid < x /mid会在运行中出错,输入num=5的时候
}else if (mid * mid< num ){
left = mid + 1;
}else{
return true;
}
}
return false;
}
}
总结
此类题目要求:有序,找一个位置
解法:使用2个边界指针,通过比较条件,如nums[mid] > target,不断缩小边界指针之间的范围。
核心代码
class Solution {
public int search(int[] nums, int target) {
if (nums[0] > target || nums[nums.length -1] < target){
return -1;
}
int left = 0;
int right = nums.length;
while(left < right){
int mid = left + ((right - left ) >> 1);
if (nums[mid] > target){
right = mid;
}else if (nums[mid] < target){
left = mid +1;
}else{
return mid;
}
}
return -1;
}
}
3.数组:就移除个元素很难么?
是要是删除重复元素或者将重复元素移动到一端的,都可以用双指针。
27. 移除元素
可以使用上一节left right指针的方法,但这样会丧失其他元素的相对次序:
class Solution {
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length;
while(left < right){
if(nums[left] == val){
nums[left] = nums[--right];
nums[right] = val;
}else{
left++;
}
}
return right;
}
}
应该使用快慢指针的方法:
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.length; fast ++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
}
慢指针指向新数组末端的后一个元素的位置。
26.删除排序数组中的重复项
class Solution {
public int removeDuplicates(int[] nums) {
if(nums == null){
return -1;
}
if(nums.length == 0){
return 0;
}
int slow = 1;
int val = nums[0];
for(int fast = 1; fast < nums.length; fast++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
// 每个新遇到的数,要是没有重复,则重新赋值val
val = nums[slow-1];
}
}
return slow;
}
}
283.移动零
class Solution {
public void moveZeroes(int[] nums) {
int slow = 0;
for(int fast = 0; fast < nums.length; fast++){
if(nums[fast] != 0){
nums[slow] = nums[fast];
slow++;
}
}
while(slow < nums.length){
nums[slow] = 0;
slow++;
}
}
}
小结
题目要求:待保留的元素移动到头部,舍弃的元素放在尾部。
解法:使用同向双指针,即快慢指针,将当前数组划分为3部分 [保留部分,慢指针,舍弃部分,快指针,待比较部分],直至待比较部分缩小为0。
核心代码
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.length; fast ++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
}
844.比较含退格的字符串
关键在于从后往前读,每次读取一个有效字符进行比较,有效字符指的是无法被’#'删除的字符。
class Solution {
public boolean backspaceCompare(String S, String T) {
int i = S.length() - 1;// 用于遍历S
int j = T.length() - 1;// 用于遍历T
int skipS = 0;// 记录'#'字符,用于消去前面的字符
int skipT = 0;
while(i >= 0 || j >= 0){// 有可能一个读完,一个没读完,所用用或运算
// 从末尾开始读取S中没有被'#'抹去的一个字符或读取不到并结束
while(i >= 0){
if(S.charAt(i) == '#'){
skipS++;
i--;
}else if(skipS > 0){
skipS--;
i--;
}else{
break;
}
}
// 从末尾开始读取T中没有被'#'抹去的一个字符读取不到并结束
while(j >= 0){
if(T.charAt(j) == '#'){
skipT++;
j--;
}else if(skipT > 0){
skipT--;
j--;
}else{
break;
}
}
if(i >= 0 && j >= 0){
// 2个字符串都从末尾读取到一个有效字符,进行比计较
if(S.charAt(i) != T.charAt(j)){
return false;
}
}else {
// 有一个读取了字符,有一个没读取到直接结束了
if(i >= 0 || j >= 0){
return false;
}
}
// 能进入下一轮while循环的是2个有效字符一样或2个同时读取结束
i--;
j--;
}
return true;
}
}
977.有序数组的平方
非递减,意思是有可能有重复的元素,不是严格单调递增。
关键在于,平方后,先找到最小值及其索引,将它放到新数组中,然后使用左右指针从最小值索引处向两端遍历,比较两端最小值,将最小值放入新数组中,然后指针往外拓展一步,直到有一个指针越出数组边界,然后将另一端的指针遍历放入新数组中。
class Solution {
public int[] sortedSquares(int[] nums) {
// if(nums.length == 1){
// return new int[]{nums[0] * nums[0]};
// }
// 平方后找最小值索引
int left = 0;// 记录最小值索引
int min = nums[0] * nums[0];
for(int i = 0; i< nums.length; i++){
nums[i] *= nums[i];
if(nums[i] < min){
min = nums[i];
left = i;
}
}
int[] result = new int[nums.length];
result[0] = min;
int index = 1;
// 从最小值处,使用2个指针分别向两端的值比较大小
int right = left + 1;
left--;
// 2个指针都没有超出边界时
while(left >= 0 && right < nums.length){
if(nums[left] <= nums[right]){
result[index] = nums[left];
left--;
}else{
result[index] = nums[right];
right++;
}
index++;
}
// 此时其中一个边界越界,则遍历另一个方向直至边界也越界
while(left >= 0){
result[index] = nums[left];
left--;
index++;
}
while(right < nums.length){
result[index] = nums[right];
right++;
index++;
}
return result;
}
}
小结
题目要求:2个部分的元素,逐个进行比较操作。
解法:使用同向或者反向双指针,要看这2个部分是不是源于同一个整体,题目844就是不是一个整体,而题目977是源于一个整体,所以它的指针是反向的。2个指针上的元素进行一次比较操作后,一个指针移动或2个都同时移动,至双方都移动到边界才停止。
核心代码
while(left >= 0 || right < nums.length){
while(left >= 0 && right < nums.length){
// 2个指针上的元素进行一次比较操作后,一个
// 指针移动或2个都同时移动
}
// 此时其中一个边界越界,则遍历另一个方向直至边界也越界
while(left >= 0 || right == nums.length){
left--;
}
while(left < 0 || right < nums.length){
right++;
}
}
4.数组:有序数组的平方,还有序么?
讲义地址
就是上一题
5.数组:滑动窗口拯救了你
209.长度最小的子数组
class Solution {
public int minSubArrayLen(int target, int[] nums) {
if(nums.length == 0){
return 0;
}
int start = 0;
int end = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
while(end < nums.length){
sum += nums[end];
while(sum >= target){
sum -= nums[start];
result = Integer.min(result, end - start + 1);
start++;
}
end++;
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
小结
这题的核心代码其实和 目录第三章第一个小结里面核心代码类似,都是使用了快慢指针,只不过这题的慢指针会反复移动。大循环的边界条件都是快指针不超出边界。这种不知道遍历过程会不会得出非零结果的,都要使用一个变量result,去记录结果。
904.水果成篮
class Solution {
public int totalFruit(int[] tree) {
int left = 0;
int right = 0;
int max = 0;
HashMap<Integer, Integer> hashMap = new HashMap<>();
while(right < tree.length){
int cur = tree[right];
right++;// 这行代码很关键
if(hashMap.size() <= 2){
// 存在则count+1
if(hashMap.containsKey(cur) == true){
hashMap.put(cur,hashMap.get(cur) + 1);
}else{不存在则新建
hashMap.put(cur,1);
}
}
while(hashMap.size() >2){
int abandon = tree[left];
// count-1
hashMap.put(abandon,hashMap.get(abandon) - 1);
// 如果减到0就移除
if(hashMap.get(abandon) == 0){
hashMap.remove(abandon);
}
left++;
}
// 此时的最大长度数组窗口为[left,right)
max = Integer.max(max, right - left);
}
return max;
}
}
小结
求最大数组,result设为Min或者0;
求最小数组,result设为Max;
76.最小覆盖子串
class Solution {
Map<Character, Integer> ori = new HashMap<Character, Integer>();
Map<Character, Integer> cnt = new HashMap<Character, Integer>();
public String minWindow(String s, String t) {
int left = 0;
int right = 0;
String str = "";
int min = Integer.MAX_VALUE;
for(int i = 0; i < t.length(); i++){
ori.put(t.charAt(i), ori.getOrDefault(t.charAt(i), 0) + 1);
}
while(right < s.length()){
char cur = s.charAt(right);
right++;
// window +cur 不满足条件,就加入cur
if(ori.containsKey(cur)){
cnt.put(cur, cnt.getOrDefault(cur, 0) + 1);
}
// window + cur 满足条件,则记录最小长度,并且left应该++,+到window不满足条件
while(check() && left < s.length()){
if(ori.containsKey(s.charAt(left))){
cnt.put(s.charAt(left), cnt.getOrDefault(s.charAt(left), 0) - 1);
}
if(min > right - left){
str = (String) s.subSequence(left, right);
min = right - left;
}
left++;
}
}
return str;
}
// 这里使用iterator迭代器比增强for循环效率要高
public boolean check() {
Iterator iter = ori.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Character key = (Character) entry.getKey();
Integer val = (Integer) entry.getValue();
if (cnt.getOrDefault(key, 0) < val) {
return false;
}
}
return true;
}
}
总结
hashmap常用函数:hashmap.getOrDefault(Object, 0)
滑动窗口使用双指针,left,right
核心代码
while(right < nums.length){
right++;
前几轮循环,状态 != 题目条件
if(当前状态 != 题目条件){
更新状态
}
while(当前状态 == 题目条件){
使状态达不到题目条件
left++;
}
}
6.数组:这个循环可以转懵很多人!
59.螺旋矩阵II
完成一次上下左右为一轮,我以轮数为循环,占用内存少:
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int x = 0;// 横坐标,向右为正
int y = 0;// 纵坐标,向下为正
int value = 1;// 当前值
for (int loop = n - 1; loop > 0 ; loop -= 2) {
// 上
for (int i = 0; i < loop; i++) {
matrix[y][x] = value;
value++;
x++;
}
// 右
for (int i = 0; i < loop; i++) {
matrix[y][x] = value;
value++;
y++;
}
// 下
for (int i = loop; i > 0; i--) {
matrix[y][x] = value;
value++;
x--;
}
// 左
for (int i = loop; i > 0; i--) {
matrix[y][x] = value;
value++;
y--;
}
x++;
y++;
}
if(n % 2 == 1){
matrix[n >> 1][n >> 1] = value;
}
return matrix;
}
}
别人的思路简单,代码简洁,以数字填完没有作为循环条件,用4个指针确定当前轮的边界:
class Solution {
public int[][] generateMatrix(int n) {
int l = 0, r = n - 1, t = 0, b = n - 1;
int[][] mat = new int[n][n];
int num = 1, tar = n * n;
while(num <= tar){
for(int i = l; i <= r; i++) mat[t][i] = num++; // left to right.
t++;
for(int i = t; i <= b; i++) mat[i][r] = num++; // top to bottom.
r--;
for(int i = r; i >= l; i--) mat[b][i] = num++; // right to left.
b--;
for(int i = b; i >= t; i--) mat[i][l] = num++; // bottom to top.
l++;
}
return mat;
}
}
54.螺旋矩阵
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
int l = 0;
int r = matrix[0].length -1;
int t = 0;
int b = matrix.length - 1;
int size = matrix[0].length * matrix.length;
while(list.size() < size){
for (int j = l; j <= r && list.size() < size; j++) {
list.add(matrix[t][j]);
}
t++;
for (int j = t; j <= b && list.size() < size; j++) {
list.add(matrix[j][r]);
}
r--;
for (int j = r; j >= l && list.size() < size; j--) {
list.add(matrix[b][j]);
}
b--;
for (int j = b; j >= t && list.size() < size; j--) {
list.add(matrix[j][l]);
}
l++;
}
return list;
}
}