- 数组理论基础
- 51.leetcode题号704.二分查找-1种思路2种写法
- 51.704代码实现
- ★ 52.leetcode27.移除元素-3种思路4种写法
- 52.27代码实现
- ★53.leetcode26.删除有序数组中的重复项-1种思路(与52.27相关题)
- 53.26代码实现
- 54.283移动零-2种思路(与52.27相关题)
- 54.283 代码实现
- ★★55.844.比较含退格的字符串(与52.27相关题)
- 55.844代码实现
- 56.977有序数组的平方(与52.27相关题)
- 56.977代码实现
- ★★ 57.209.长度最小的子数组(好像是目前第一个中等题)
- 57.209代码实现
- ~~★★★58.904水果成篮(暂时跳过)~~
- ~~58.904代码实现~~
- ~~59.76最小覆盖子串(暂时跳过)~~
- ~~59.76 代码实现~~
- ★60.59螺旋矩阵2
- 60.59代码实现
- ~~61.54螺旋矩阵(暂时跳过)~~
- ★62.剑指Offer29.顺时针打印矩阵
- 62.剑指29代码实现
704.二分查找
第一次错误
思路: 方法一: 27.移除元素 思路:因为元素的顺序可以改变,而且只需要原地修改数组空间O(1),返回修改后数组的新长度即可。不需要考虑数组中超出新长度后面的元素。 1>自己想的从后覆盖 2>书上写的暴力解法,用两个循环 4>leetcode对双指针的优化 方法一:自己想的,从后覆盖 方法二:书上的暴力双层循环 方法三:leetcode的双指针法 方法四:leetcode方法三优化,与方法一思路类似 26.删除有序数组中的重复项 思路: 由于题目要求删除数组中的重复元素,因此输出数组的长度一定小于等于输入数组的长度,我们可以把输出的数组直接写在输入数组上。可以使用双指针:左指针指向当前遇到的第一个重复元素,右指针指向下一个不重复的位置。 ①一开始右指针比左指针多走一步,左指针指向赋好值的新元素,如果右指针指向的元素不等于自己的前一个元素,说明找到了下一个不重复值,它一定是输出数组的一个元素,则左指针加一,将右指针指向的元素复制到左指针位置,然后将右指针继续后移, ②如果右指针指向的元素等于自己的前一位,它不能在输出数组里,此时左指针不动,右指针右移一位。 整个过程保持不变的性质是:区间 [0,left]中的元素都不重复。当右指针遍历完输入数组以后,left+1的值就是输出数组的长度。 思路不变,写法优化一点点。 283.移动零 方法一:暴力双层循环 方法二:我的双指针覆盖后补0 方法三:官方交换 844.比较含退格的字符串 工具:复习String类和字符数组的互相转换 2>官方解法一重构字符串 工具: ②String类的charAt和equals ★3>官方解法二双指针 4> ①每次循环,都判断当前索引走到的位置。两个索引只要有一个>=0的情况下进入循环(之所以不写&&而写||是因为,会出现一种情况,一个字符已经走完,另一个字符还有可比较的字符,说明不相等。但是&&会导致跳出循环,不对多出的字符判断不等而错误)。两个索引都从字符尾部开始遍历字符 错误三次: 方法一:自己想的暴力法,用了一个toCharArray(); 方法二:官方解法一,用StringBuffer类 方法三: 双指针从后往前分别遍历两个字符串,用一个变量记录#出现的次数,根据变量值对后续遍历到的字符进行删除或比较。双层循环 977.有序数组的平方 2> 自己想的双指针,从中间位置开始。 ★3>官方的方法三双指针,官方采用从两头开始遍历,逆序对新数组赋值。重点理解无需处理某一指针移动至边界的情况。 方法一:暴力 方法二:官方双指针一,从中间最小的正负数开始,正序赋值新数组。需要考虑边界 方法三:按照官方思路自己写的,与方法二类似。但不是官方的最优写法,并没有领悟到不考虑边界条件的精髓 ★方法三:官方思路官方写法,重点理解。仔细考虑不需要边界条件 209.长度最小的子数组 ★★2>官方方法二前缀和+二分查找理解了好久 ?问题,二分查找要找到具体的target,但是题目要找>=的,如果找不到=target二分查找返回的是什么?要怎么找到子数组的尾部 方法一:我的暴力双层循环 ★方法一:官方的暴力写法更简洁,但是超出时间限制无法通过样例 ★★方法二:前缀和+二分查找。难理解 ★方法三:滑动窗口个人觉得类似之前的双指针 思路:理解题意为,fruits[i] = 2说明第i颗树上是第2种水果,需要找到一个子数组里,只有两种水果的果树,因为每颗果树只能摘一个果子。所以要求这个子数组要足够长就可以摘到最多数量的两种类型水果。 题解的这些语句完全不懂。暂时跳过 看不懂的语句 59.螺旋矩阵2 2>官方法一,模拟 方法一:我的暴力解法,循环一次是完成一圈的赋值。每次将四个边分开赋值 方法二:官方法一,模拟 剑指29.顺时针打印矩阵 ★2>按层模拟 方法一:模拟,错了两次在处理空数组上 方法二:按层模拟,与n*n的矩阵不同,注意边界条件
二分查找是查找有序的数组,定义left,right,mid来确定数组下标,
1>自己想的
①[left,right]表示target存在的范围,左右都是闭区间,即left,right有可能是target,所以会取到。mid表示所求区间范围的中间位置
②考虑边界问题,while(left <= right),因为left == right时是有意义的,如一个元素的数组找这一个数
③如果nums[mid] == target 说明找到了返回找到的坐标mid。如果nums[mid]>target,说明target在left到mid之间。则更新right=mid-1,因为mid位置已经判断不可能是target,当前寻找范围变为[left,mid-1]
2>书上写的
考虑如果定义target在一个左闭右开的区间[left,right)说明right不可能为target,不取这个right,代码需要改变的部分,right=nums.length,while(leftclass Solution {
public int search(int[] nums, int target) {
int l = 0;
int r = nums.length-1;
int mid = 0;
while(l <= r){
mid = l+(r-l)/2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
r = mid-1;
}else{
l = mid+1;
}
}
return -1;
}
}
★ 52.leetcode27.移除元素-3种思路4种写法
①所以可以每次删除值为val的元素时,每次将最后一个位置的数字对要删除的位置数字覆盖,再对数组长度减一。
②但是需要注意,这种情况下需要考虑最后一位是否也是val,如果是的话可以直接数组长度减一,等于从尾部开始删除。
③可以定义两个变量指代这两个数,end指在末尾要去覆盖val,覆盖后数组长度减一,如果自身是val则数组长度直接减一,end--继续寻找。
star是指从头寻找要被覆盖的数val位置,被覆盖后++寻找下一个等于val的位置。
④考虑边界问题,当star与end相遇时,说明star寻找val已经到达新的尾部,如果此时等于val,则数组长度减一。则循环过程满足star <= end
第一个循环遍历数组元素,第二个循环更新数组,得每次都挪后面所有的元素
3>快慢指针法
书上写的双指针法也是leetcode官方的双指针法,用两个快慢指针,在一个循环内完成两个for循环的工作,优化了2>的暴力写法
①因为2>,3>方法都在按照顺序一个个赋值,但是题目要求顺序可以改变。4>类似1>对要覆盖的直接覆盖,无需一一赋值。
②与我自己思考的1>不同之处在于对尾部遇到val的处理,我是数组长度--,end--。官方是赋值后left位置不变,right--继续给left赋新的数组尾部,直到left不为val为止。
③边界情况也不同,left==right时就停止不进入循环while(left
复杂度分析,时间O(N),空间O(1)。
最坏情况,没有val,两指针总共遍历数组一次class Solution {
public int removeElement(int[] nums, int val) {
int end = nums.length-1;
int star = 0;
while(star <= end){
if(nums[end] == val){
end--;
}else{
if(nums[star] != val){
}else{
//开始覆盖操作
nums[star] = nums[end];
end--;
}
star++;
}
}
return end+1;
}
}
这个运行结果比较惊讶,可能是数据小,双层循环也很快
复杂度分析,时间O(N^2),空间O(1)。
最坏情况,n-1个val,运行次数外循环遍历n次,内循环依次大概n-1次public int removeElement_1(int[] nums, int val){
int size = nums.length;
for(int i=0; i
复杂度分析,时间O(N),空间O(1)。
最坏情况,没有val,两指针最多都遍历数组一次public int removeElement_1(int[] nums, int val) {
int left = 0;
int right = 0;
while(right < nums.length){
if(nums[right] == val){
//右指针遇到旧数组元素,不用加入新数组,所以left不动,right+1
}else{
nums[left] = nums[right];
left++;
}
right++;
}
return left;
}
复杂度分析,时间O(N),空间O(1)。
最坏情况,没有val,两指针总共遍历数组一次 public int removeElement_3(int[] nums, int val) {
int left = 0;
int right = nums.length;
while(left < right){
if(nums[left] == val){
nums[left] = nums[right-1];
right--;
}else{
left++;
}
}
return left;
}
★53.leetcode26.删除有序数组中的重复项-1种思路(与52.27相关题)
//虽然过了,但是有个问题就是数组为空时会返回1,可以写法上优化一下
class Solution {
public int removeDuplicates(int[] nums) {
int left = 0,right = left+1;
while(right
加一个处理空数组的判断
修改先给left加一再赋值为先赋值再left加一。从闭区间变为左闭右开,[0,left)。就与官方写法完全一致了
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length == 0){
return 0;
}
int left = 1,right = 1;
while(right < nums.length){
if(nums[right-1] != nums[right]){
nums[left] = nums[right];
left++;
}
right++;
}
return left;
}
}
54.283移动零-2种思路(与52.27相关题)
思路:
1>暴力法
①双层循环,内层循环将0之后的元素全部往前挪一位,并将最后一位赋值为0
②外层循环遍历数组,判断哪一位是0进入内循环
2>双指针法
①可以把移动零后的前不为0的数看为新数组,将旧数组中不为0的数依次提出来赋给新数组。
②赋值过程中范围[0,left)左闭右开,因为left需要被right赋值以后才是新数组成员,且赋值后++等待right找到下一位后赋值
③全部赋值完成后将剩余位全部赋0即可。
3>官方双指针交换
与2>双指针类似,但是不是覆盖后补0,是不为0的与之前所有元素交换
暴力循环写错了两次,以后不能省略写暴力循环
public void moveZeroes_1(int[] nums) {
//暴力循环试一下
int n = nums.length;
for(int i=0; i
跑了三遍不知道这个内存消耗咋回事这么大class Solution {
public void moveZeroes(int[] nums) {
int left=0,right=0;
while(left < nums.length){
if(right < nums.length){
if(nums[right] != 0){
nums[left] = nums[right];
left++;
}
right++;
}else{
nums[left] = 0;
left++;
}
}
}
}
class Solution {
public void moveZeroes(int[] nums) {
int n = nums.length, left = 0, right = 0;
while (right < n) {
if (nums[right] != 0) {
swap(nums, left, right);
left++;
}
right++;
}
}
public void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
★★55.844.比较含退格的字符串(与52.27相关题)
思路:
1>自己想的暴力写法
分为两步,先处理字符串里出现#时,删除#的前一个字符。处理完毕后再两两比较字符串是否相等
①将字符串转为字符数组,然后依次遍历,
②当遍历到#时,需要退格,即删除#和他的前一个字符,用后面的其后的字符全部向前挪两位进行覆盖,并且length-2。
③注意!!!,此处在写的时候发现一个问题例如“cd##",d#需要用后一个#覆盖,这种情况下在覆盖完成后,需要再次判断被覆盖位(#及其前一个位置)是否为#。如果不是,才遍历下一项。此时i在上一个检测出#的位置,即每次覆盖后,遍历的i–,倒退一格判断。
④再依次向后遍历,重复操作,内循环用来删除退格符及前一个字符,即前移两位,外循环用来遍历字符数组。特殊情况,数组只有一个字符#,字符串为空。或第一个位置为#,后续所有字符前移一位。
⑤比较字符串是否相等时,若遍历两个字符数组,每一位都相等,但长度不等也为不相等。
①String转化成字符数组
String.toChararray(),String类成员toCharArray函数可将其转换成字符数组。
char[]cd = c.toCharArray();c为字符串。
②将字符数组转换成String
法1:利用String类的构造函数,直接在构造String时完成转换。
char[] data = {'a','b','c'}; String str = new String(data);
法2:调用String类的valueOf函数转换。String.valueOf(char[] ch);
分为两步,先处理字符串里出现#时,删除#的前一个字符。处理完毕后再两两比较字符串是否相等
①简单理解就是建立一个StringBuffer类的对象ret来存储处理后的字符串,
②在这个过程中, 用char ch = String.charAt(i)按位检索字符串的第i位,如果不为#则进栈,就是用ret.append(ch)将该字符追加到ret。
③如果检索到为#,将栈顶弹出,ret.deleteCharAt(ret.length() - 1)因为StringBuffer是一个可修改的类,内容有变化后,长度会自动变化。遍历完成后将ret转化为字符串并返回,return ret.toString()
④对两个字符串都进行上述操作后,用String.equals(String s)比较是否相同
①StringBuffer类的append方法和deleteCharAt方法
StringBuffer类教程
StringBuffer类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuffer类用到的方法:
public StringBuffer append(String s)将指定的字符串追加到此字符序列。
int length()返回长度(字符数)。
deleteCharAt方法是用来删除StringBuffer字符串指定字符索引的方法,其中delete(inta,intb)方法:包含两个参数,使用时删除索引从a到b(包括a不包括b)的所有字符。”
StringBuffer.deleteCharAt(i)删除i位置的字符,i范围[0.length-1]
String toString()返回此序列中数据的字符串表示形式。
//StringBuffer相关用法示例
StringBuffer str = new StringBuffer();
System.out.println(str);
System.out.println(str.length());
str.append("添加第一段字符add");
System.out.println(str);
System.out.println(str.length());
str.deleteCharAt(3);
System.out.println(str);
System.out.println(str.length());
str.append("添加第二段字符add");
System.out.println(str);
System.out.println(str.length());
String类的方法教程
String类用到的方法
char charAt(int index)返回指定索引处的 char 值。
boolean equals(Object anObject)将此字符串与指定的对象比较。
官方写法,双层循环,内层两个循环分别每次从后往前遍历s和t字符串。对skip进行操作,判断出可比较的数后跳出内层循环,进行比较。外层循环保证两个字符串里至少有一个索引>=0就要进入循环继续比较。理解了官方的思路以后,我试图自己实现逻辑。 因为官方写的双层循环,所以想尝试写一个单层循环,
逻辑关系很麻烦写起来,但是主要是为了锻炼逻辑能力,所以多尝试
②如果当前字符是是#则skip++,索引--。
如果不为#但是skip此时不为0,说明当前位置需要删除,则不进行比较,skip--意思该位删除,直接索引--跳过
只有当遇到不为#且此时skip==0的情况下,索引会停止,说明找到了可比较的字符,进行比较。如果相同索引都--比较下一对。如果不同说明字符不相等返回false
★③但是这种写法碰到了一个问题,当索引有一个<0时,说明成功遍历结束且自己比较过的都与对方相等。但是另一方字符串的问题在于,此时剩余遍历的可比较的字符如果只有#则忽略,意为可以删除,如果是可比较的其他字符说明长度不等返回false。
写法上判断应该是,索引>=0的那个字符的`skip==0且不为#,说明此位不是#也不用被删除,需要参与比较,说明两字符串不相等,返回false。
case"ab##""c#d#"(因为难以处理只有一个索引结束时的情况)
case"isfcow#""isfcog#w#"(比较判断时只判断了skip==0,但是#也不能参与判断)
case"a#c""b"判断比较不为#的时候粗心笔误
case"c#a#c""c"笔误,又写反了t和s
class Solution {
public int deleteBackspace(char[] s){
//删除退格符
int i = 0;
int sLength = s.length;
while(i
//写一个build函数,接受字符串,返回处理完退格符后的字符串,
public String build(String str){
StringBuffer bu = new StringBuffer();
for(int i=0; i
class Solution {
public boolean backspaceCompare(String s, String t) {
int pointS = s.length()-1;
int pointT = t.length()-1;
int skipS = 0;
int skipT = 0;
while(pointS>=0 || pointT>=0){//用||一个走完了,另一个没走完且有可比较的数为不相等情况。
while(pointS>=0){
if(s.charAt(pointS) == '#'){
pointS--;
skipS++;//记录要删除的个数
}else if(skipS>0){
pointS--;
skipS--;//删除该位
}else{
break;//不是#也不用被删除,参与比较
}
}
while(pointT>=0){
if(t.charAt(pointT) == '#'){
pointT--;
skipT++;//记录要删除的个数
}else if(skipT>0){
pointT--;
skipT--;//删除该位
}else{
break;//不是#也不用被删除,参与比较
}
}
if(pointS>=0 && pointT>=0){
if(t.charAt(pointT) != s.charAt(pointS)){
return false;
}
}else{
if (pointS>= 0 || pointT>= 0) {
//因为完全确定走到这里一定是两个索引走走完<0或者一个走完另一个在可比较数字
return false;
}
}
pointS--;
pointT--;
}
return true;
}
}
方法四:可能就是一些闲的发慌自己写超复杂逻辑关系,为了单层循环的双指针。错了四次,其中粗心错了两次,乡村错题本本人,逻辑关系复杂,不建议
class Solution {
public boolean backspaceCompare(String s, String t) {
//双指针指向两个字符串从后往前遍历
int skipS = 0;
int skipT = 0;//记录遍历过程中#出现的次数,代表要删除的字符个数
int pointS = s.length()-1;
int pointT = t.length()-1;//指针用来遍历数组
while(pointS>=0 || pointT >= 0){//||是防止一个已经走完了,另一个还在遍历并不匹配的字符这种不相等的情况误判为相等。
if(pointS >= 0 ){
if( s.charAt(pointS) == '#'){
skipS++;
pointS--;
}else{
if (skipS != 0) {//删除
//可以直接跳过不比较
//
pointS--;
skipS--;
}
}
}
if(pointT >= 0 ) {
if (t.charAt(pointT) == '#') {
skipT++;
pointT--;
} else {
if (skipT != 0) {//删除
//可以直接跳过不比较
pointT--;
skipT--;
}
}
}
if(pointT>=0 && pointS>=0){//说明是走到了可以比较的没有被删除的位置
if((skipT ==0 && skipS == 0) && (s.charAt(pointS) != '#' && t.charAt(pointT) != '#')){
//说明当前位置无需删除,可以进行比较
if(s.charAt(pointS) != t.charAt(pointT)){
return false;
}else{
pointT--;
pointS--;//说明此位置比较成功,比较下一对
}
}
}else{
if(pointT >= 0 ){//说明s走完了,t没有
if(t.charAt(pointT) != '#'){
if(skipT == 0){
return false;//参与比较的没被删除也不是#
}
else{//需要删除的位
pointT--;
skipT--;
}
}else{//这一位是#
pointT--;
skipT++;//记录还要删除的位数
}else if(pointS >= 0 ){//说明t走完了,s没有.两个都走完的直接跳出
if(s.charAt(pointS) != '#'){
if(skipS == 0){
return false;//参与比较的没被删除也不是#
}
else{//需要删除的位
pointS--;
skipS--;
}
}else{//这一位是#
pointS--;
skipS++;//记录还要删除的位数
}
}
}
}
return true;
}
}
56.977有序数组的平方(与52.27相关题)
思路:
1> 自己想的暴力解法
①先遍历一遍数组,给每个数组都平方。
②然后冒泡排序。时间复杂度O(n^2)
官方的暴力方法类似,调用了Arrays.sort(ans)直接排序无需自己写。
与官方的方法二相同,把正数和负数分成两个子数组,然后进行一个类似的归并排序。
①两个指针分别开始时指到最大的负数和最小的正数的位置,然后平方后比较大小,更小的就填入新数组,正指针填入后++,负指针填入后–。
②一直到其中一个走到头<0或>length-1,另一个就直接统统开平方直接填入新数组。返回新数组
时间复杂度为O(n)
自己写的误区在于其实不需要判断是否剩余全是正数或全是负数,无需(nums[neg]>=0)和else if(nums[pos]<0)这两种判断情况。
因为不论原数组是否全是正数(or负数)的情况,都只需判断他的平方大小,如果更大就逆序填入新数组。因为两头数字的平方一定比中间数字的平方大。
若指针未判断的数字全是正数或全是负数,也是只有一头平方大,依然只需要比较两个指针所指位置平方的大小即可。不需要考虑左指针一定要指负数,右指针一定要指正数。
public static int[] sortedSquares(int[] nums) {
for(int i = 0; i
class Solution {
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int pos = 0;
int neg = -1;//为什么不从0开始,因为有可能是只有正数的数组
int[] ans = new int[n];
int index = 0;//遍历新数组的索引
//记录最小的负数位置
for(int i=0; i
//不简洁的方法三写法
class Solution {
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int pos = n-1;//尾
int neg = 0;//头
int index = n-1;//逆序赋值
int[] ans = new int[n];
while(neg<=pos){//不相遇
if(nums[neg]>=0){
//说明原数组全是正数,或neg负指针已经走到正数范围,无需再动
ans[index] = nums[pos]*nums[pos];
pos--;
}else if(nums[pos]<0){
ans[index] = nums[neg]*nums[neg];
neg++;
}else if(nums[pos]*nums[pos] > nums[neg]*nums[neg]){
ans[index] = nums[pos]*nums[pos];
pos--;
}else{
ans[index] = nums[neg]*nums[neg];
neg++;
}
index--;
}
return ans;
}
}
class Solution {
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
for(int i=0,j=n-1,index=n-1; i<=j; index--){//不相遇
if(nums[i]*nums[i] > nums[j]*nums[j]){
ans[index] = nums[i]*nums[i];
i++;
}else{
ans[index] = nums[j]*nums[j];
j--;
}
}
return ans;
}
}
//main
for(int i:num){
System.out.print(" "+i);
}
System.out.println();
num = sortedSquares_2(num);
for(int i:num){
System.out.print(" "+i);
}
System.out.println();
★★ 57.209.长度最小的子数组(好像是目前第一个中等题)
思路:
1>暴力*O(n^2)
①外循环遍历数组的元素作为子数组头,内循环从子数组头开始连加之后位置,直到和>=target,记录加了几个数,即子数组的长度。
②若内循环一直加的项数== 原数组长度,且和
④外循环将当前子数组长度与之前记录的比较,若更小则替换。
官方的暴力用到工具:
Integer.MAX_VALUE表示int数据类型的最大取值数:2 147 483 647
Integer.MIN_VALUE表示int数据类型的最小取值数:-2 147 483 648
Math.min(ans, j - i + 1)返回两数中的较小值
重点理解二分查找函数Arrays.binarySearch。未找到时返回的不是-1而是-(left+1)
与普通的二分查找不同,此处的二分查找大于等于某个数的第一个位置的功能
如果数组[1,2,5]要找6,返回下标-4。要找0,返回下标-1。要找4,返回下标-3。(我理解的返回值意思是,找不到且target如果要在数组里,位置应该是第几个数,例如找不到的0应该插入到第一个位置,以此类推)
处理>=target的第一个位置即为**-bound-1==left(为二分查找结束时,left的位置即为>target的最小位置)**
查找库函数Arrays.binarySearch的实现
3>滑动窗口
个人困惑:end不回退
夭折的想法
类似滑动窗口但是思路不清楚,因为实现起来感觉很费时间且时间复杂度好像还是O(n^2)(因为没有理解到end不回退,而且虽然写法是双层循环。实际两个指针都是各遍历一次数组)
可以第一次找到>=target的子数组时,记录头尾位置【left,right】,下次子数组范围【left+1,right+1】在此长度中连加,若到达right+1之前就>=target,则调整right位置。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
int count = 0;
int sum = 0;
int minCount = 0;
for(int i=0; i
无法通过这次的样例,对官方的代码补充。外循环内部,内循环结束的位置,补充了一句判断即可在第一次外循环时,判断不可行的代码,避免O(n^2)时,n过大导致超出时间限制。if(sum < target){//说明j走到了n+1
break;//说明此时的子数组头一直加到原数组尾都不满足>=target
}
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
if(n == 0){
return 0;
}
int count = Integer.MAX_VALUE;
//开始我认为赋值n更好,更小。而且也可以代表一个初始的最大值。去和更小的子数组长度比较
// 但是如果原数组全部累加都不满足>=target的话,
// 这种情况count也==n但是并没有找到子数组需要返回0。或者恰好找到了,又要返回n
for(int i = 0; i < n; i++){
int sum = 0;
for(int j = i; j
不自己写binarySearch,可以调用Java库函数Arrays.binarySearch(sum,target);
//57.2前缀和+二分查找,自己实现一个二分查找
//二分查找
public static int binarySearch(int[] nums, int target){//模仿Arrays.binarySearch
//返回找到的target的坐标
int n = nums.length;
int left = 0;
int right = n-1;
int mid = (right-left)/2;
while(left<=right){//考虑边界条件<=,想到特殊情况,只有一个元素也进入
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid-1;
}else{
left = mid+1;
}
mid = left+(right-left)/2;
}
//走到这里说明没找到,返回
return -(left+1);
}
//二分+前缀和的官方写法
public static int minSubArrayLen_2(int target, int[] nums){
int n = nums.length;
int count = Integer.MAX_VALUE;//用来记录子数组长度
if(n<1){
return 0;//空数组
}
//做前缀和数组sum
int[] sum = new int[n+1];//sum[i]表示nums数组的前i个数的和[0,i-1]
for(int i=1; i<=n; i++){
sum[i] = sum[i-1]+nums[i-1];
}
for(int i=1; i<=n; i++){//外层循环遍历数组sum,确定子数组头.
int tar = target + sum[i-1];
//根据子数组头变化target,子数组头从i开始时,每一项都等于加了前i-1项
int index = binarySearch(sum,tar);//用来找子数组尾部下标
if(index<0){//sum有n+1项
//说明没找到=tar的,根据返回值还原>tar的最小位置,
// 找不到tar时,因为return -(left+1)。index可能的范围[-1,-n-2]
index = -index-1;//处理前取值范围[-1,-n-2]处理后[0,n+1]其中n+1在sum属于越界
}
if(index <= n){//找到的>=tar的下标没有越界,说明存在
//index不可能=sum[i-1](target=0的时候等于)
//所以index>=i
count = Math.min(count, index-(i-1));//i-1为子数组头的前项
//这个最小位置一定是<=n的,不然说明整个数组里都不存在
}
}
return count == Integer.MAX_VALUE ? 0:count;
}
class Solution {
public int minSubArrayLen(int target, int[] nums){
int n = nums.length;
if(n == 0){
return 0;
}
int count = Integer.MAX_VALUE;
int start = 0;
int end = 0;
int sum = 0;
while(end
★★★58.904水果成篮(暂时跳过)
自己想的错误法:
List
58.904代码实现
59.76最小覆盖子串(暂时跳过)
Map
59.76 代码实现
★60.59螺旋矩阵2
思路:
1>我的暴力写法
①根据n为偶数和n为奇数有两种不同的写法,观察图形,偶数时,中心位置为一个2*2的小矩阵。奇数时,中心位置为n^2。如n=3,n/2=1,循环一次,n^2填入中心位置[n/2][n/2]
②矩阵从外到内,将一圈顺时针遍历作为循环的一次,每次将上、右、下、左边界分成四个部分依次遍历。如图,每次遍历的边界为左闭右开[min,max)
③例如,n = 4时,n/2=2循环两次,顺时针遍历两圈。第一次遍历的行列为,0行,3列,3行,0列。循环第二次时,遍历的行列为,1行,2列,2行,1列。定义一组变量存放每次循环的边界值,min,max分别代表当前顺时针的一圈,上下左右的边界值。
④第一次循环max=3,min=0。上边界,行=min,列[min,max);右边界,列=max,行[min,max);下边界行=max,列[max,min);左边界,列=min,行[max.min)。第一次赋值时,要填入的数字为1,每次赋值后+1自增。第二次循环max–=2,min++=1,依次类推
①定义一个数组directions如图,用来进入下一个方向。行变化+directions数组的第一列,即+dire[direIndex][0],列变化+dire数组的第二列,即+dire[direIndex][1]。
②当即将赋值的下一个位置越界,或遇到了已经赋值过的不为0的位置时,进入下一个方向。
③即+的数据,进入dire的下一行,direIndex++,但是dire数组只有四行,代表了四个边界。direIndex需要在[0,3]循环取值,则每次direIndex++时写为(direIndex+1)%4,用来处理越界后,第一行开始循环取值的情况
3>官方法二,按层模拟。与我的暴力一思路写法基本相同
class Solution {
public int[][] generateMatrix(int n) {
int[][] nums = new int[n][n];
//把一圈看做一次循环赋值,第一次循环的起始位置为0,0。
//需要按顺序遍历0行,3列(n-1),3行,0列。
//第二次循环遍历1行,2列,2行,1列。起始位置1,1.
//到达中心点时停止,例如n/2=2,循环两次。若为奇数,中心点自己填
if(n%2 != 0){
nums[n/2][n/2] = n*n;
}
int min = 0;
int max = n-1;//控制每次循环的边界行列值
int row = 0;//行的坐标
int col = 0;//列的坐标
int num = 1;
for(int i=0; i
int maxNum = n*n;//curNum的最大值,也是结束时的值
int curNum = 1;//遍历每个位置要赋值的值,每次赋值后++
int row = 0;//矩阵行
int column = 0;//矩阵列
int direIndex = 0;//遍历dire的行
int[][] matrix = new int[n][n];//新建螺旋矩阵
int dire[][] ={{0,1},{1,0},{0,-1},{-1,0}};//改变顺时针方向的矩阵
while(curNum <= maxNum){//maxNum作为最后一个值,是闭区间。
matrix[row][column] = curNum++;
//定义两个行列下一个位置,在真正取到之前判断是否要改变方向
int nextRow = row + dire[direIndex][0];
int nextColumn = column + dire[direIndex][1];
if(nextRow >= n || nextColumn >= n ||nextRow < 0 || nextColumn < 0 || matrix[nextRow][nextColumn] != 0){
//前四个判断是最外圈的循环越界的情况,最后一个是内圈遍历到外圈已经赋值的情况
//改变顺时针方向
direIndex = (direIndex+1)%4;
}
//遍历++,继续走
row = row + dire[direIndex][0];
column = column + dire[direIndex][1];
}
return matrix;
61.54螺旋矩阵(暂时跳过)
★62.剑指Offer29.顺时针打印矩阵
思路:与60题解法思路基本一致
1>使用模拟方法
①创建数组dire[][]={{0,1},{1,0},{0,-1},{-1,0}};四个元素分别代表,行不变列+1;行+1列不变;行不变列-1;行-1列不变;
②需要得到二维数组的行列长度来确定遍历边界,数组行数为:array.length。数组列数为:array[0].length或者array[1].length //某一行的长度就是列数。二维数组元素个数:行数*每行元素个数(即列数)= array.length * array[0].length
③因为此处是按顺时针输出到新的一维数组并返回,要怎么排除异己打印过的数,可以建立一个与输入的二维数组同行同列的boolean数组,用来记录该位置是否被打印,boolean类型初始值为false,遍历后赋值true。
①根据示例输入的矩阵并不都是n*n的矩阵,不方便使用我的暴力按层模拟,因为只定义了每层的边界min,max;但可以使用官方的按层模拟,分别约束四条边的边界值,定义top,right,left,bottom。
②写好代码后发现一个问题,对于3*3这种奇数的正方形矩阵,赋值是无法遍历到中心的那一个位置,因为每一边都是左闭右开,都取不到这个位置。
③所以,改进。重点考虑当left=right=top=bottom这种情况,还有只余一竖行(left=right)或一横行(top=bottom)的情况。怎么在遍历一次后不重复遍历进入边的遍历。
④遍历完上边和右边以后,需要判断一下,只有left
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0){
int[] array=new int[0];
return array;
}
int dire[][] = {{0,1},{1,0},{0,-1},{-1,0}};
int direIndex = 0;//遍历dire行的索引
int row = 0;//行索引
int column = 0;//列索引
int maxNum = matrix.length*matrix[0].length;//二维数组元素个数
int nums[] = new int[maxNum];//新数组及索引
boolean[][] visited = new boolean[matrix.length][matrix[0].length];//与matrix同行同列
int i = 0;
while(i < maxNum) {
nums[i] = matrix[row][column];//遍历
visited[row][column] = true;
int nextRow = row + dire[direIndex][0];
int nextColumn = column + dire[direIndex][1];
if (nextRow >= matrix.length || nextColumn >= matrix[0].length || nextRow < 0 || nextColumn < 0 || visited[nextRow][nextColumn] == true) {
//超出边界,或下一个位置已经遍历过。需要改变遍历方向
direIndex = (direIndex+1)%4;
}
row += dire[direIndex][0];
column += dire[direIndex][1];
i++;//遍历次数+1
}
return nums;
}
}
class Solution {
public int[] spiralOrder(int[][] matrix) {
//排除空数组的情况
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return new int[0];
}
//
int top = 0;
int left = 0;
int right = matrix[0].length-1;//最大列数
int bottom = matrix.length-1;//最大行数
int row = 0;
int column = 0;
int[] nums = new int[(matrix[0].length)*(matrix.length)];
int i = 0;//nums的索引
while(left <= right && top <= bottom){
//上边界赋值
column = left;
while(column <= right){
nums[i++] = matrix[top][column++];
}
//右边界
row = top+1;
while(row <= bottom){
nums[i++] = matrix[row++][right];
}
if(left < right && top < bottom){
//下
column = right-1;
while(column > left){
nums[i++] = matrix[bottom][column--];
}
//左
row = bottom;
while(row > top){
nums[i++] = matrix[row--][left];
}
}
top++;
left++;
right--;
bottom--;
}
return nums;
}
}



