1630. 等差子数组
public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
ArrayList<Boolean> ans = new ArrayList<>();
for(int i=0;i<l.length;i++){
int[] arrs = Arrays.copyOfRange(nums, l[i], r[i]+1);
Arrays.sort(arrs);
int j=1;
while (arrs.length>1&&j<arrs.length){
if (arrs[j] - arrs[j -1] != arrs[1] - arrs[0]) {
ans.add(false);
break;
}
j++;
}
if(j==arrs.length) ans.add(true);
}
return ans;
}
高效率的做法
一次遍历找到最大最小值 那么公差 d = (max-min)/(len-1) 不能整除直接return false
然后再遍历,第i项 a[i] = a[0]+id 不如就算出来i=(a[i]-a[0])/d 若不能整除false 或者出现重复i false Hash[d]记录是否1~d都出现过 真是等差一定会0~len-1各出现一次的
哇:两次遍历2O(n)+一个O(n)空间的数组就OK了,完全不需要排序
public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
ArrayList<Boolean> ans = new ArrayList<>();
int[] Hash = new int[nums.length];
for (int i = 0; i < l.length; i++) {
Arrays.fill(Hash, 0);
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
for (int j = l[i]; j <= r[i]; j++) {
min = Math.min(min, nums[j]);
max = Math.max(max, nums[j]);
}
if ((max - min) % (r[i] - l[i]) != 0) {
ans.add(false);
} else if (max == min) {//所有数字都一样
ans.add(true);
} else {
int d = (max - min) / (r[i] - l[i]);
boolean flag = true;
for (int j = l[i]; j <= r[i]; j++) {
if((nums[j]-min)%d!=0) {//注意这里也得判断
flag = false;
break;
}
int t = (nums[j] - min) / d;
if (Hash[t] == 1) {//等差数列一定每个都一份的 否则一定会有重复
flag = false;
break;
} else Hash[t] = 1;
}
ans.add(flag);
}
}
return ans;
}
1. 两数之和
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<>();//存放元素下表
for (int i = 0; i < nums.length; i++) {
if(map.containsKey(target-nums[i])){//map.containsKey()时间复杂度是O(1) !!放心大胆用
return new int[]{i,map.get(target-nums[i])};
}
map.put(nums[i],i);//记录下标
}
return null;
}
知识点:java map.containsKey()时间复杂度是O(1), 访问大胆用即可
用map做Hash,简直不要太爽
2. 两数相加
节点个数100,一眼大数,自己再写一次大数吧。确实很简单的大数
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode zero = new ListNode(0);
ListNode head = new ListNode(-1);//头结点使得操作统一
ListNode tail = head;
int carry = 0;
while (l1 != null || l2 != null) {
if (l1 == null) l1 = zero;
if (l2 == null) l2 = zero;
//System.out.println(l1.val+" "+l2.val);
tail.next = new ListNode((l1.val + l2.val + carry) % 10);
tail = tail.next;
carry = (l1.val + l2.val + carry) / 10;
l1 = l1.next;
l2 = l2.next;
}
if (carry!=0){
tail.next = new ListNode(carry);
}
return head.next;
}
3. 无重复字符的最长子串
剑指里好像刷过这题,好像就是dp+Hash, 直接上
public int lengthOfLongestSubstring(String s) {
int max = 0;
int l = 0,r = 0;//首尾双指针 多好 (子串一定是连续的呀)
int[] Hash = new int[128];//ASCII只有128个
char[] chars = s.toCharArray();//习惯用数组
for (r = 0; r < chars.length; r++) {
while (Hash[chars[r]]>0){
Hash[chars[l++]]--;
}
Hash[chars[r]]++;
max = Math.max(max,r-l+1);
}
return max;
}
果然是做过,一点难度没有觉察到
但是看题解发现了java的HashSet专门用来判重
(HashSet底层就是HashMap,也就是专门的Hash表去了下重复,所以contains方法也是O(1) 直接看源码就map.containsKey(o))
public int lengthOfLongestSubstring(String s) {
int max = 0;
int l = 0,r = 0;//首尾双指针 多好 (子串一定是连续的呀)
HashSet<Character> set = new HashSet<>();
char[] chars = s.toCharArray();//习惯用数组
for (r = 0; r < chars.length; r++) {
while (set.contains(chars[r])){
set.remove(chars[l++]);//remove应该也是O(1) Hash表嘛 都是O(1)
}
set.add(chars[r]);
max = Math.max(max,r-l+1);
}
return max;
}
确实好用~
4. 寻找两个正序数组的中位数
先牺牲空间归并一下,时间完全满足O(m+n),这也能过,太水了~ 还hard
public class LC4 {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int[] ans = new int[nums1.length + nums2.length];
int k = 0;
int i = 0, j = 0;
while (i < nums1.length && j < nums2.length) {
if(nums1[i]<nums2[j]){
ans[k++]=nums1[i];
i++;
}else {
ans[k++]=nums2[j];
j++;
}
}
while (i<nums1.length) ans[k++] = nums1[i++];
while (j<nums2.length) ans[k++] = nums2[j++];
/*if(k%2==1) return ans[k/2];
return (ans[k/2]+ans[k/2-1])/2.0;*/
//小trick
return (ans[(k-1)/2]+ans[k/2])/2.0;
}
public static void main(String[] args) {
test(new int[]{1,3},new int[]{2});
test(new int[]{1,2},new int[]{3,4});
test(new int[]{1,3},new int[]{2,3,4,5});//1 2 3 3 4 5
}
static void test(int[] A,int[] B){
LC4 t = new LC4();
double ans = t.findMedianSortedArrays(A, B);
System.out.println(ans);
}
}
先想到不存储,直接找到第(l1+l2)/2旁边就截止,不就将空间省下来了吗,没想到边界竟然这么麻烦
还是在用到了奇偶都可以用 (ans[(k-1)/2]+ans[k/2]) /2.0 得到中位数的trick
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length + nums2.length;
int m1 = (n - 1) / 2;
int m2 = n / 2;
int k = 0, ans1 = 0, ans2 = 0;
//不管奇偶 找这个两个取平均即可
int i1 = 0, i2 = 0;
while (i1 < nums1.length && i2 < nums2.length) {
if(nums1[i1] < nums2[i2]) {
if (k == m1) {
ans1 = nums1[i1];
}
if (k == m2) {
ans2 = nums1[i1];
return (ans1 + ans2) / 2.0;
}
i1++;
k++;
} else {
if (k == m1) {
ans1 = nums2[i2];
}
if (k == m2) {
ans2 = nums2[i2];
return (ans1 + ans2) / 2.0;
}
i2++;
k++;
}
}
while (i1 < nums1.length) {
if (k == m1) {
ans1 = nums1[i1];
}
if (k == m2) {
ans2 = nums1[i1];
return (ans1 + ans2) / 2.0;
}
i1++;
k++;
}
while (i2 < nums2.length) {
if (k == m1) {
ans1 = nums2[i2];
}
if (k == m2) {
ans2 = nums2[i2];
return (ans1 + ans2) / 2.0;
}
i2++;
k++;
}
return -1;
}
trick:
总长度 n = l1 + l2
奇数: k/2
偶数:k/2和(k-1)/2
都遍历到下标 k/2
或者 都 k/2和(k-1)/2取平均 (奇数时二者相等 就自然统一了)
或者直接记录pre和now即可
if (n1 < l1 && nums1[n1] < nums2[n2])改成if (n1 < l1 && (n2>=l2|| nums1[n1] < nums2[n2]))
另一个越界了也是我+,小技巧,省了大代码
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int l1 = nums1.length;
int l2 = nums2.length;
int len = l1 + l2;
int n1 = 0, n2 = 0;//
int pre = 0, now = 0;//记录上次的结果
int T = len / 2 +1 ;//找到这就够了 下标len/2,但其实是(len/2)+1多个
while (T-- > 0) {
pre = now;
if (n1 < l1 && (n2>=l2|| nums1[n1] < nums2[n2])) {//nums2越界了也是我+ k2>=l2写前面短路,否则nums2[k2]会越界
now = nums1[n1++];
}else {
now=nums2[n2++];//不用想 肯定走这边 (只有2种逻辑可以走)
}
}
if((len&1)==1) return now;//奇数
return (now+pre)/2.0;
}
5. 最长回文子串
public String longestPalindrome(String s) {
char[] arr = s.toCharArray();
int l = arr.length;
int[][] dp = new int[l][l];
int start=0,maxl=1;//维护一个起始下标和长度作为最终结果返回
for (int i = 0; i < dp.length; i++) {
dp[i][i]=1;//长度为1
if(i>0&&arr[i]==arr[i-1]) {
dp[i-1][i]=1;//长度为2 千万注意i
start=i-1;//i-1不是i 小细节害死人
maxl=2;
}
}
for(int len =2;len<l;len++ ){//长度为3开始遍历 [i,i+len]长度为len+1
for (int i = 0; i+len < l; i++) {
int j = i+len;
if(arr[i]==arr[j]) dp[i][j] = dp[i+1][j-1];
else dp[i][j]=0;//直接就是0 不是回文子串
if(dp[i][j]==1&&len+1>maxl){//len+1才是实际长度
maxl=len+1;
start=i;
}
}
}
return new String(arr,start,maxl);//直接取数组部分初始化为String
}
代码有点长,想优化一下,总感觉优化了个寂寞:
//感觉代码有点长 休整一下
public String longestPalindrome(String s) {
char[] arr = s.toCharArray();
int l = arr.length;
boolean[][] dp = new boolean[l][l];
for (int i = 0; i < dp.length; i++) dp[i][i] = true;//长度为1
int start = 0, maxl = 1;//维护一个起始下标和长度作为最终结果返回
for (int len = 2; len <= l; len++) {//长度真的从2开始遍历
for (int i = 0; i + len - 1 < l; i++) {
int j = i + len - 1;//[i,i+len-1] 长度正好是len
if (arr[i] != arr[j]) dp[i][j] = false;//非回文子串
else if (len == 2) dp[i][j] = true;//长度为2的边界
else dp[i][j] = dp[i + 1][j - 1];
if (dp[i][j] && len > maxl) {//len+1才是实际长度
maxl = len;
start = i;
}
}
}
//return new String(arr, start, maxl);//直接取数组部分初始化为String
return s.substring(start, start + maxl);//参数竟然是首尾下标
}
时间O(n^2) , 空间O(n^2)
接下来就是老老实实看题解,一步一步发掘最优解了
此法,时间还是O(n^2),但是空间变成O(1)了
注意回文中心有两种,长度为1或者长度为2
长度为1如: a => bab 也就是奇数子串
长度为2如: aa=> baab 也就是偶数子串
所以得以回文中心为下标专门写一个扩散函数
我的写法:
public String longestPalindrome(String s) {
char[] arr = s.toCharArray();
int l = arr.length;
int begin = 0, maxl = 1;
for (int i = 0; i < l - 1; i++) {
Integer[] ok1 = expandAroundCenter(i,i,arr);
Integer[] ok2 = expandAroundCenter(i,i+1,arr);
if(ok1!=null&&ok1[1]-ok1[0]+1>maxl){
maxl = ok1[1] - ok1[0]+1;
begin = ok1[0];
}
if(ok2!=null&&ok2[1]-ok2[0]+1>maxl){
maxl = ok2[1] - ok2[0]+1;
begin = ok2[0];
}
}
return s.substring(begin, begin + maxl);//参数竟然是首尾下标 当然还是左闭右开
}
public Integer[] expandAroundCenter(int i, int j, char[] arr) {
int k = 0;
while (i - k >= 0 && j + k < arr.length && arr[i-k]==arr[j+k]) k++;
k--;
Integer[] ans = null;
if(k>-1) ans = new Integer[]{i-k,j+k};//返回首尾下标
return ans;
}
题解写法:
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
//★ 奇偶奇迹般地统一了 i=1 aaaa和aaab 举例验证 是正确的
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
public int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return right - left - 1;//(right-1)-(left+1)+1
}
巧妙之处在于,不管结果是奇数还是长偶数长,都给你一个式子统一了:
start = i - (len - 1) / 2;
end = i + len / 2;
eg: i=1 “aaaa”
expandAroundCenter(1,1) => 3
expandAroundCenter(1,2) => 4
len取4 此时 :
start = i - (len - 1) / 2 = 1 - 3/2 = 0
end = i + len / 2 = 1+4/2 = 3
正确
eg: i=1 “aaab”
expandAroundCenter(1,1) => 3
expandAroundCenter(1,2) => 2
len取3 此时 :
start = i - (len - 1) / 2 = 1 - 2/2 = 0
end = i + len / 2 = 1+3/2 = 2
正确
理解:
start = i - (len - 1) / 2; //i左偏了 所以 (len-1)
end = i + len / 2; //右边正常
//然后举例论证 2个不同的例子(1奇1偶)能正确 基本全局都正确了