链表,主要是递归下去的过程;
无论是链表还是数组,都是 每次 把 当前数组/链表 分成两部分。。
class Solution {
// nlogn 的排序-> 归并
//归并一般是对两个有序数组或链表而言,那么怎么对一个序列用?
//结合递归,递归到区间只有一个元素的时候,单个有序的元素肯定有序,递归返回的过程中进行归并,就是有序的序列两两归并了
//数组 递归 到最后是 L==R,
// 链表递归呢? 应该在递归找中点的过程中,随时让 中点的下一个节点为空,这样递归返回的条件就是 head==null ||head.next== null
public ListNode sortList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode mid = midNode(head);
ListNode midNext = mid.next;
mid.next = null;//这样其实是把链表节点拆成了一个一个单个的点
ListNode l = sortList(head);
ListNode r = sortList(midNext);
return merge(l, r);
}
public ListNode merge(ListNode h1,ListNode h2){
ListNode newHead = new ListNode(0);
ListNode cur = newHead;
while(h1!=null && h2 != null){
if(h1.val < h2.val){
cur.next = h1;
h1 = h1.next;
}else{
cur.next = h2;
h2 = h2.next;
}
cur = cur.next;
}
if(h2 !=null){
h1 = h2;
}
while(h1 !=null){
cur.next = h1;
h1 = h1.next;
cur = cur.next;
}
return newHead.next;
}
//注意这个链表找中点的 两个指针初始的值
public ListNode midNode(ListNode h){
ListNode slow = h;
ListNode fast = h.next;
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
//荷兰国旗和按某数分左中右,快排是这个的加强
//<,=,>
public class QuickSort {
public static void quickSort(int[] arr) {
if(arr==null|| arr.length<2) {
return;
}
quickSort(arr,0,arr.length-1);
}
/**
* @param sourceArray:需要排序的数组
* @param left:排序数组左边界,一般为:0
* @param right:排序数组右边界,一般为:length - 1;
* less:小于参照元素区域的最右边边界:less = p[0] - 1;
* more:大于参照元素区域的最左边边界:more = p[1] + 1;
* p[0]:等于参照元素区域的最左边边界;
* p[1]:等于参数元素区域的最右边边界;
* 小于参照元素区域:[Left ~ less];
* 等于参照元素区域:[p[0] ~ p[1]];
* 大于参照元素区域:[more ~ right];
*/
public static void quickSort(int[] sourceArray,int left,int right ) {
if (left < right) {
swap(sourceArray, left + (int) (Math.random() * (right - left + 1)), right);
// p 数组中: p[0] 表示等于区域的左边界,p[1] 表示等于区域的右边界,
// 左边区域:L ~ p[0] - 1;右边区域: p[1] + 1 ~ R;
int[] p = partition(sourceArray, left, right);
quickSort(sourceArray, left, p[0] - 1);
quickSort(sourceArray, p[1] + 1, right);
}
}
public static int[] partition(int[] sourceArray, int left, int right) {
int less = left - 1;
int more = right;
int cur = left;
while (cur < more) {
// 以数组最后一个元素为标准,将整个数组划分为 小于、等于、大于 三个部分
if (sourceArray[cur] < sourceArray[right]) {
swap(sourceArray, ++less, cur++);
} else if (sourceArray[cur] > sourceArray[right]) {
swap(sourceArray, --more, cur);
} else {
cur++;
}
}
swap(sourceArray, more, right);
return new int[]{less + 1, more};
}
public static void swap(int[] sourceArray, int left, int right) {
int tmp = sourceArray[left];
sourceArray[left] = sourceArray[right];
sourceArray[right] = tmp;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = {43, -31, 10, -38, -42, -2, 22, 29, 30, 15, -60, -50, -13, 26, 3, 22, 27, 24, 18, 18, 42, -40, 22, 8, 33, -52, -70, -55, 31, 42, 82, 19, -8, 8, 41, -35, 59, 65, -23, 3, -34, 65};
System.out.println("原数组为:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
quickSort(arr);
System.out.println("\n排序后数组为:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
快排+ 堆
第K个,前K个,都可以用快排的思想来做,
快排思想
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0) {
return new int[0];
} else if (arr.length <= k) {
return arr;
}
// 原地不断划分数组
partitionArray(arr, 0, arr.length - 1, k);
// 数组的前 k 个数此时就是最小的 k 个数,将其存入结果
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = arr[i];
}
return res;
}
void partitionArray(int[] arr, int lo, int hi, int k) {
// 做一次 partition 操作
int m = partition(arr, lo, hi);
// 此时数组前 m 个数,就是最小的 m 个数
if (k == m) {
// 正好找到最小的 k(m) 个数
return;
} else if (k < m) {
// 最小的 k 个数一定在前 m 个数中,递归划分
partitionArray(arr, lo, m-1, k);
} else {
// 在右侧数组中寻找最小的 k-m 个数
partitionArray(arr, m+1, hi, k);
}
}
// partition 函数和快速排序中相同,具体可参考快速排序相关的资料
// 代码参考 Sedgewick 的《算法4》
int partition(int[] a, int lo, int hi) {
int i = lo;
int j = hi + 1;
int v = a[lo];
while (true) {
while (a[++i] < v) {
if (i == hi) {
break;
}
}
while (a[--j] > v) {
if (j == lo) {
break;
}
}
if (i >= j) {
break;
}
swap(a, i, j);
}
swap(a, lo, j);
// a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
return j;
}
void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
堆的做法
class Solution {
//维护一个以出现次数为key的 小根堆(K)那么大,因此有可以随时去掉小的,维持K个最大的
//所以这个小根堆 以 一个数组为元素,数组里面是数字和对应的次数
//统计次数用hasnmap,
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int i =0;i<nums.length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
//小根堆
PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>(){
public int compare(int[] o1, int[] o2){
return o1[1]-o2[1];
}
});
//遍历map,把元素加进堆了,注意,堆只能有K个元素
for(Map.Entry<Integer,Integer> entry: map.entrySet()){
if(heap.size() == k){
if(heap.peek()[1] < entry.getValue()){
heap.poll();
heap.offer(new int[]{entry.getKey(),entry.getValue()});
}
}else{
heap.offer(new int[]{entry.getKey(),entry.getValue()});
}
}
//堆转到数组中
int[] res = new int[k];
for(int i=0;i<k;i++){
res[i] = heap.poll()[0];
}
return res;
}
}
这两个题都是快排思想的应用,只是指针都可以简化,未必都需要三个指针;
移动0. 快排思想的简化版,直接不等于0的往前移动,后面的全都置为0;
class Solution {
//划分区域,想借鉴快排的思想,用一个指针,相当于left记录不为0区域的当前位置
//每次遇到不为0的就放在left++的位置
public void moveZeroes(int[] nums) {
if(nums==null||nums.length==0) return;
int left = -1;
int cur =0;
while(cur<nums.length){
if(nums[cur]!=0){
nums[++left] = nums[cur];
}
cur++;
}
while(left<nums.length-1){
nums[++left] =0;
}
}
}
剑指offer21
class Solution {
//想法还是快排的partition的思路,可以当作只有小于和大于没有等于区
public int[] exchange(int[] nums) {
int left = 0, right = nums.length-1;
while(left < right){
if((nums[left] & 1 )== 0){
//注意&要加括号
swap(nums, left,right--);
}else{
left++;
}
}
return nums;
}
public void swap(int[] nums, int i,int j){
int tmp = nums[j];
nums[j] = nums[i];
nums[i] = tmp;
}
}
//方法一:借助哈希结构
public static boolean findDuplicate(int[] arr) {
if(arr.length==0) {
return false;
}
HashSet<Integer> set = new HashSet<Integer>();
for(int i=0;i<arr.length;i++) {
if(set.contains(arr[i])) {
System.out.println(arr[i]);
return true;
}
set.add(arr[i]);
}
return false;
}
//方法二:不使用额外的空间
public static boolean find(int[] arr) {
if(arr.length==0) {
return false;
}
for(int i=0;i<arr.length;i++) {
//比如index= 3,存的数字是4,那就把这个数字放在4这个位置上,然后把换出来的数字继续交换
while(arr[i] !=i) {
if(arr[i] == arr[arr[i]]) {
System.out.print(arr[i]);
return true;
}else {
swap(arr,i,arr[i]);
}
}
}
return false;
}
public static void swap(int[] arr,int i,int j) {
int temp = arr[i];
arr[i]= arr[j];
arr[j] = temp;
}
/**
* 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
* 完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
*/
public class T4FindInArray {
public static boolean hasNum(int[][] arr,int num) {
int row = 0;
int col = arr[0].length-1;
// int i=0;
// int j = col-1;
while(row <= arr.length-1 && col>=0) {
if(arr[row][col]==num) {
return true;
}
if(arr[row][col]<num) {
row++;
}else if(arr[row][col]>num) {
col--;
}
}
return false;
}
public boolean isStraight(int[] nums) {
Arrays.sort(nums);
int count = 0;
for(int i = 0; i < nums.length-1; i++){
if(nums[i] == 0){
count++;
continue;
}
if(nums[i] == nums[i+1]) return false;
int tmp = nums[i+1] - nums[i];
while(tmp > 1){
if(count == 0) return false;
count--;
tmp--;
}
}
return true;
}
public boolean validMountainArray(int[] A) {
if (A == null || A.length < 3) {
return false;
}
int n = A.length;
int low = 0, high = n - 1;
while (low < high && A[low] < A[low + 1]) {
low++;
}
while (low < high && A[high] < A[high - 1]) {
high--;
}
if (low == n - 1 || high == 0) {
return false;
}
return low == high ? true : false;
}
一个错误解法,就是想顺着找,也不知道为啥不对
public boolean canJump(int[] nums) {
boolean res = false;
for(int i = 0;i<nums.length-1;i++){
if(nums[i] == 0) break;
int index = i;
while(index < nums.length){
if(nums[i] == 0) break;
index += nums[i];
if(index > nums.length) break;
if(index == nums.length){
res = true;
}
}
}
return res;
}
贪心解法:就是不是一定要跳到那个点才叫到达,只要在最大区间里面的点,都可以成为到达。
class Solution {
//为什么可以想到贪心呢,因为它不是要精确的跳到呢个点,而是只要那个点在可以跳到的范围内就行
//所以就是尽可能的 远 就行
public boolean canJump(int[] nums) {
int max = 0;
for(int i = 0;i< nums.length;i++){
if(i>max) return false;/跳不到这个点,后面的就更到不了了
max = Math.max(i+nums[i],max);
if(max >= (nums.length-1)) return true;
}
return false;
}
}
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null, tail = null;
int carry = 0;
while (l1 != null || l2 != null) {
int n1 = l1 != null ? l1.val : 0;
int n2 = l2 != null ? l2.val : 0;
int sum = n1 + n2 + carry;
if (head == null) {
head = tail = new ListNode(sum % 10);
} else {
tail.next = new ListNode(sum % 10);
tail = tail.next;
}
carry = sum / 10;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry > 0) {
tail.next = new ListNode(carry);
}
return head;
}
}
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new LinkedList<>();
if(nums.length<3) return res;
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
int left = i+1;
int right = nums.length-1;
if(i>0 && nums[i]==nums[i-1]) continue;
while(left<right){
int sum = nums[left]+nums[i]+nums[right];
if(sum==0){
res.add(Arrays.asList(nums[i],nums[left],nums[right]));
while(left<right && nums[left+1]==nums[left]) left++;
while(left<right && nums[right-1]==nums[right]) right--;
left++;
right--;
}else if(sum<0) left++;
else right--;
}
}
return res;
}
class Solution {
//觉得跟直接找三数之和的那个提差不多
//先排序,再对每个数字后面用双指针
//当然如果这个数字本身已经大于target,就不用比较了
public int threeSumClosest(int[] nums, int target) {
int res = Integer.MAX_VALUE;
int ress = Integer.MAX_VALUE;
Arrays.sort(nums);
for(int i = 0;i<nums.length;i++){
int l = i+1;
int r = nums.length-1;
while(l<r){
int num = nums[i]+nums[l]+nums[r];
if(Math.abs(num-target) < res) {
res = Math.abs(num-target);
ress = num;
}
if(num<=target) l++;
if(num>=target) r--;
}
}
return ress;
}
}
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
class Solution {
//1 想清楚 一个 长度为n的数组,最小的没有出现的正数,最大是n+1,区间就是[1,n+1]
//2 剩下的就是没有出现,hash很正常,数组变hash也常见,一般以index为key,数组中的值为val;
//这里 如果不是追求 O(1)的空间没必要改原数组,再new个数组,也不用改负数啥的了
public int firstMissingPositive(int[] nums) {
int len = nums.length;
//遍历负改大
for(int i = 0; i < len; i++){
if(nums[i] <= 0) nums[i] = len+2;
}
//再遍历,这里要注意啦,值是不可信的,因为可能在这次遍历的过程中改了
for(int i = 0; i < len; i++){
int tmp = Math.abs(nums[i]);
if(tmp <= len){
int index = tmp - 1;
nums[index] = -Math.abs(nums[index]);
}
}
//找结果
//int res = len + 1;
for(int i = 0; i < len; i++){
if(nums[i] >= 0){
return i+1;
}
}
return len+1;
}
}
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
class Solution {
//用set好简单, 但空间不满足
//数组 改成map, 下标作为key,出现就把对应下标的值改成负的, 若想改时发现已经负了, 就是这个数
public int findDuplicate(int[] nums) {
int len = nums.length;
int res = 0;
for(int i = 0; i < len; i++){
int num = Math.abs(nums[i]);
if(nums[num] < 0){
res = num;
break;
}
else{
nums[num] = -nums[num];
}
}
return res;
}
}
这种缺失,重复 不是用map(或者以数组为map,就是用 位运算)
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
class Solution {
//set最简单但肯定不让用
//另一个做法 抽屉原理
public List<Integer> findDuplicates(int[] nums) {
//不交换 ,改为负
List<Integer> res = new ArrayList<>();
if(nums == null ||nums.length == 0) return res;
for(int i = 0; i < nums.length; i++){
int num = Math.abs(nums[i]);
if(nums[num-1] < 0){
res.add(num);
continue;
}else{
nums[num-1] = -nums[num-1];
}
}
return res;
}
}
一样的,没啥意思,看看41 得了
快手一面,正整数数组,串成最大的数
class Solution {
//这题按自己的思路是把数字排序,如果第一位相等再去比较第二位,但是很难实现
//那入股考虑把数字拼成字符串呢?为什么要这样考虑? 如果直接拼成一个最大的数,干嘛还要你返回一个字符串,既然是字符串
//那就直接考虑成字符串,两个字符串比较的 compareTo方法,不就是相比较第一位,第一位相等再比较第二位
//返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和 参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。
public String largestNumber(int[] nums) {
//先转string
String[] strNums = new String[nums.length];
for(int i=0;i<nums.length;i++){
strNums[i] = String.valueOf(nums[i]);
}
//其实通过写这个就可以实现一个String数组按ascii码来排序,然后再拼接起来就行了
Arrays.sort(strNums,new Comparator<String>(){
public int compare(String a,String b){
String str1 = a+b;
String str2 = b+a;
//核心就是按两个字符串起来哪个更大来排序
//a-b;升序,b-a降序
return str2.compareTo(str1);
}
});
//如果第一位是0,就直接return0就行了
if(strNums[0].equals("0")) return "0";
StringBuilder sb = new StringBuilder();
for(int i =0;i<strNums.length;i++){
sb.append(strNums[i]);
}
return sb.toString();
}
}
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
class Solution {
//贪心
/**
1 要找字典序更大的排列 -> 低位大数换高位小数 -> 左小和右大交换
2 大的尽量少才是下一个排列 -> 交换的过程尽量在右边
右边大数中较小的那个, 换完之后,对右边的数升序排序
具体做法,从后往前找第一个升序对,i,j,那么j之后的必然是降序排的
此时小的数是最左的,大的数是构成升序最右的,却未必是最合适的,
真正适合交换的, 是 从这个最右的大数往右看 最小的那个
交换之后,后面的数 此时是逆序的, 本来就是降序,又把最小的那个换到最右了,此时把后面的数字逆序即可
*/
public void nextPermutation(int[] nums) {
int len = nums.length;
if(len == 1) return;
int l = -1, r = -1;
for(int i =len-1; i>=0; i--){
if( l!=-1 && r!= -1) break;
for(int j = i; j < len; j++){
if(nums[i] < nums[j]){
l = i;
r = j;
break;
}
}
}
if(l == -1 || r == -1){
reverseNums(nums,0,len-1);
}else{
int k = -1;
for(int i = len-1; i >= r; i--){
if(nums[i] > nums[l]){
k = i;
break;
}
}
swap(nums, l, k);
reverseNums(nums,r,len-1);
}
}
public void swap(int[] nums, int l, int r){
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
}
public void reverseNums(int[] nums, int l, int r){
while(l <= r){
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
l++;
r--;
}
}
}
所以即使是在递归过程中,也要及时判断条件是否满足,尽量减少递归次数
class Solution {
//DFS,
// 矩阵中每个点都可以作为开始结点
// 用过的点不能再用,所以要加标记,还要在回溯回来的过程中取消标记
//超时怎么办,就尽量减少递归次数
boolean res = false;
public boolean exist(char[][] board, String word) {
if(board == null || word == null) return false;
char[] chars = word.toCharArray();
int len = chars.length;
int m = board.length;
int n = board[0].length;
int[][] used = new int[m][n];
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
//不需要所有的都遍历,只要找到一个true就可以立即返回
if(board[i][j] == chars[0]){
dfs(board,chars,0,i,j,used);
}
if(res){
return true;
}
}
}
return false;
}
public void dfs(char[][] board, char[] chars, int index,int i, int j,int[][] used){
if(index == chars.length){
res = true;
return ;
}
if(i<0 || i>board.length-1|| j<0 || j>board[0].length-1) return ;
if(used[i][j] == 1) return ;
if(board[i][j] != chars[index]) return ;
used[i][j] = 1;
//这里也要随时判断来减少递归的次数
dfs(board,chars,index+1,i+1,j,used);
if(res) return;
dfs(board,chars,index+1,i-1,j,used);
if(res) return;
dfs(board,chars,index+1,i,j+1,used);
if(res) return;
dfs(board,chars,index+1,i,j-1,used);
if(res) return;
used[i][j] = 0;
}
}
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
两个数组,一个记录前几个的乘积,一个记录后面的乘积
class Solution {
public int[] constructArr(int[] a) {
int len = a.length;
if(len == 0) return new int[]{};
int[] pre = new int[len];
pre[0] = a[0];
int[] post = new int[len];
post[len-1] = a[len-1];
//if(len == 0 || a == null) return new int[];
for(int i = 1; i < len; i++){
pre[i] = pre[i-1] * a[i];
}
for(int i = len-2; i >= 0; i--){
post[i] = post[i+1] * a[i];
}
int[] res = new int[len];
res[0] = post[1];
res[len-1] = pre[len-2];
for(int i = 1; i < len-1; i++){
res[i] = pre[i-1] * post[i+1];
}
return res;
}
}
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。空间常数。
class Solution {
/**
实际上这题不知道在考啥? 遍历一遍,记录下标就好,再一次遍历改0
只是这样子分行列数组的话,空间不太优化,所以可以加标记:
1-- 第一次遍历时,可以把0所在的行和列设置为 非常大或者非常小的值,第二次直接改
2-- 第一次遍历时可以 把 0所在的行列的头标记一下,第二次遍历就直接遍历第一列和第一行
*/
public void setZeroes(int[][] matrix) {
int R = matrix.length;
int C = matrix[0].length;
Set<Integer> rows = new HashSet<Integer>();
Set<Integer> cols = new HashSet<Integer>();
// Essentially, we mark the rows and columns that are to be made zero
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
if (matrix[i][j] == 0) {
rows.add(i);
cols.add(j);
}
}
}
// Iterate over the array once again and using the rows and cols sets, update the elements.
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
if (rows.contains(i) || cols.contains(j)) {
matrix[i][j] = 0;
}
}
}
}
}
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
进阶:你可以设计并实现时间复杂度为 O(n) 的解决方案吗?
class Solution {
//如果不要求时间复杂度,可以排序后从前往后遍历即可
public int longestConsecutive(int[] nums) {
if(nums == null) return 0;
int len = nums.length;
if(len == 1) return 1;
Arrays.sort(nums);
int res = 0;
int cur = 1;
int tmp ;
while(cur < len){
tmp = 1;
while(cur < len && (nums[cur]== nums[cur-1]+1 || nums[cur] == nums[cur-1])){
if(nums[cur] != nums[cur-1]) tmp++;
cur++;
}
cur++;
res = Math.max(res, tmp);
}
return res;
}
}
如果时间复杂度是线性的:
空间换时间,跳过的思路跟做法一是一样的。
甚至这里解法一的时间性能要比二好。。。。估计跟测试用例的数据有关。。
class Solution {
//如果要求线性的复杂度,弄个辅助数组,把数字存在对应的下标里,只是这里数组会非常非常大,不合适
//另一个思路, hashset,然后每个值去列举以它为开始的长度
//按照排序的思路,跳过,也就是 像上个思路一样, 从x开始往后找,直到它不连续,那么下一次从这个不连续的开始找,而不是从x+1开始
//因为从x+1开始只能找到比从x开始小一个的,所以跳过,用hash辅助是同理的
public int longestConsecutive(int[] nums) {
if(nums == null) return 0;
int len = nums.length;
if(len == 1) return 1;
int res = 0;
// 一:空间换时间
Set<Integer> dict = new HashSet<>();
for(int num: nums) dict.add(num);
//二:减少没必要的比较
for(int i = 0; i < len; i++){
//以x为开始的区间,已经找到,这个区间内的每一个值,都没必要再找,所以,要跳过这个区间,特征就是:
if(dict.contains(nums[i]-1)) continue;
int tmpLen = 1;
int tmpNum = nums[i] + 1;
while(dict.contains(tmpNum)){
tmpLen ++;
tmpNum++;
}
res = Math.max(tmpLen, res);
}
return res;
}
}
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
class Solution {
//两个数组,一个从前往后,一个从后往前,或者一个数组先从前往后,再从后往前
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
if(n == 0) return new int[0];
if(n == 1) return nums;
int[] preProduct = new int[n];
int[] postProduct = new int[n];
preProduct[0] = 1;
postProduct[n-1] = 1;
for(int i = 1; i < n; i++){
preProduct[i] = preProduct[i-1] * nums[i-1];
}
for(int i = n-2; i >= 0 ; i--){
postProduct[i] = postProduct[i+1] * nums[i+1];
}
for(int i = 0; i < n; i++){
preProduct[i] = preProduct[i] * postProduct[i];
}
return preProduct;
}
}
优化: 降低空间复杂度,现在除了用于输出的preProduct,还有一个post,所以空间是n。
优化可以一个用数组,另外一个用一个整数迭代着表示即可
class Solution {
//两个数组,一个从前往后,一个从后往前,或者一个数组先从前往后,再从后往前
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
if(n == 0) return new int[0];
if(n == 1) return nums;
int[] preProduct = new int[n];
int postProduct = 1;
preProduct[0] = 1;
//前向数组
for(int i = 1; i < n; i++){
preProduct[i] = preProduct[i-1] * nums[i-1];
}
//后向用一个整数保存,并且将前后乘积一起放在 preProduct里面
for(int i = n-2; i >= 0 ; i--){
postProduct = postProduct * nums[i+1];
preProduct[i] = postProduct * preProduct[i];
}
// for(int i = 0; i < n; i++){
// preProduct[i] = preProduct[i] * postProduct[i];
// }
return preProduct;
}
}
解法一: 转为字符串类型,然后再去判断是否是回文结构
可以去回顾一下String StringBuffer StringBuilder
public boolean isPalindrome(int x) {
String reversedStr = (new StringBuilder(x + "")).reverse().toString();
return (x + "").equals(reversedStr);
}
解法二: 从回文的定义出发,翻转后半部分数字,然后再比对
public boolean isPalindrome(int x) {
if(x<0 || (x%10==0 && x!= 0)) return false;
int reverseNum = 0;
//怎么确定翻转到了一半?
//若是回文数,那么翻转一半后半部分的数要大于(奇数位),等于(偶数位)前半部分
while(x>reverseNum){
int yu = x % 10;
reverseNum = reverseNum * 10 + yu;
x = x / 10;
}
//若为奇数位个数,那么后半部分会比前半部分多一个尾数
return x==reverseNum || x == reverseNum/10;
}
与回文数算法思想相同,但需要判断32位整数是否溢出(溢出分正负考虑即可),因为int类型占4个字节,取值范围为:-2147483648~2147483647,所以有可能出现原数为1999999999,如果反转就溢出了。
public int reverse(int x) {
int reverseNum = 0;
while(x != 0){
int pop = x % 10;
x = x / 10;
//正数溢出的情况
if(reverseNum>Integer.MAX_VALUE/10 || (reverseNum==Integer.MAX_VALUE/10 && pop>7)) return 0;
//负数
if(reverseNum<Integer.MIN_VALUE/10 ||(reverseNum==Integer.MIN_VALUE/10 && pop<-8)) return 0;
reverseNum = reverseNum * 10 +pop;
}
return reverseNum;
}
大数在左 小数在右,若不符合,则要相减
public int romanToInt(String s) {
if(s==null) return 0;
int res = 0;
int preNum = getValue(s.charAt(0));
//大数在左 小数在右,若不符合,则要相减
for(int i = 1;i<s.length();i++){
int num = getValue(s.charAt(i));
if(preNum < num) res -= preNum;
else res += preNum;
preNum = num;
}
//别忘了最后一个
res+= preNum;
return res;
}
private int getValue(char ch) {
switch(ch) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return 0;
}
}
如果使用for循环,在小于等于x的范围内逐个找值的话,虽然是一种解决办法,但是运行会超出时间限制。
计算平方根最好的办法是牛顿迭代法:
public int mySqrt(int x) {
if (x == 0) {
return 0;
}
double C = x, x0 = x;
while (true) {
double xi = 0.5 * (x0 + C / x0);
if (Math.abs(x0 - xi) < 1e-7) {
break;
}
x0 = xi;
}
return (int)x0;
}
当然也可以考虑二分法:对0到x二分,查找第一个平方大于等于x的数
public int mySqrt(int x) {
int l = 0, r = x, ans = -1;
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;
}
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
public int numSquares(int n) {
//状态: 1-n的数字的最少完全平方数
int[] dp = new int[n+1];
//取最小的,初始化时可以用最大值
Arrays.fill(dp, Integer.MAX_VALUE);
//平方数表
int count = (int)Math.sqrt(n)+1;
int[] sqrtNum = new int[count];
for(int i = 0;i< count;i++){
sqrtNum[i] = i*i;
}
//初始化
dp[0] = 0;
for(int i=1;i<=n;i++){
for(int j = 1;j<count;j++){
if(sqrtNum[j] > i) break;
else{
dp[i] = Math.min(dp[i],dp[i- sqrtNum[j]]+1);
}
}
}
return dp[n];
}
二进制的不同位数: 可以逐位异或来求
public int hammingDistance(int x, int y) {
return Integer.bitCount(x ^ y);
}
将x,y按位异或得到i,将问题转化为求i的二进制位中1的个数count。异或后移位,根据1的个数,判断有几个不同。
public int hammingDistance(int x, int y) {
int i = x ^ y;
int count = 0;
while(i != 0){
//
if((i & 1) == 1)
count++;
i = i >> 1;//右移动一位
}
return count;
题目描述:已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。
不要使用系统的 Math.random() 方法。
大到小可以直接用,直到生成满足范围的数,因为是等概率的;
小到大,用加法(如这里的 7+7-1,不能够等概率),先用乘法转成比大数大的,然后就变成大到小了
比如:
rand7 -》rand10 :(rand7() -1)*7 + rand7();
rand5->rand7 : (rand5-1)*5 + rand 5()
总之: 小到大: (randX()-1) * X +randX(); 然后再用方法一或者方法二;
首先 rand7()-1得到的数的集合为 { 0,1,2,3,4,5,6 }
再乘 7 后得到的集合 为{0,7,14,21,28,35,42}
后面 rand7()得到的集合B为{1,2,3,4,5,6,7}
相加后得到的是1-49的等概率的数
然后就变成了 rand49 到 rand10,大到小,只要把大数转成小的即可
其实是数据转换的操作,但是要保证等概率的问题
方法一:
public int rand10() {
int num = (rand7() - 1) * 7 + rand7();
// 只要它还大于0,那你就给我不断生成吧
while (num > 10)
num = (rand7() - 1) * 7 + rand7();
// 返回结果,+1是为了解决 40%10为0的情况
return num;
}
方法一:我们的函数会得到 1~49 之间的数,而我们只想得到 1~10之间的数,这一部分占的比例太少了,
简而言之,这样效率太低,太慢,可能要 whilewhile 循环很多次,那么解决思路就是舍弃一部分数,
舍弃 41~49,因为是独立事件,我们生成的 1~40之间的数它是等概率的,我们最后完全可以利用 1~40之间的数来得到 1~10之间的数。
也就是方法一中浪费了计算资源,所以不让只要满足10 就返回,而是满足40就返回
public int rand10() {
// 首先得到一个数
int num = (rand7() - 1) * 7 + rand7();
// 只要它还大于40,那你就给我不断生成吧
while (num > 40)
num = (rand7() - 1) * 7 + rand7();
// 返回结果,+1是为了解决 40%10为0的情况
return 1 + num % 10;
}
快速幂
class Solution {
//递归,相当于指数二分的做法。知识要注意,N为奇数和偶数,
public double myPow(double x, int n) {
long N = n;
return N >=0? quickMul(x,n):1.0/quickMul(x,-n);
}
public double quickMul(double x, long n){
if(n==0) return 1.0;
//这个过程是,9/2=4,除了第一个数,中间都是偶数,而且到了最后1,因为都是1,
double y = quickMul(x,n/2);
return n%2==0? y*y:y*y*x;
//实际上,是奇数,只有第一个数和中间也是奇数的时候会额外乘x,最后得1才会用到。
//例如77,38,19,9,用这种方式,奇数的时候都会多乘一个
}
}
lass Solution {
public int nthUglyNumber(int n) {
int a = 0, b = 0, c = 0;
int[] dp = new int[n];
dp[0] = 1;
for(int i = 1; i < n; i++) {
int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
dp[i] = Math.min(Math.min(n2, n3), n5);
if(dp[i] == n2) a++;
if(dp[i] == n3) b++;
if(dp[i] == n5) c++;
}
return dp[n - 1];
}
}
求出区间[a,b]中所有整数的质因数分解。
/**
7 * 分析:对n进行分解质因数,应先找到一个最小的质数k
8
9 * 最小的质数:即“2”。2是最小的质数,即是偶数又是质数,然后按下述步骤完成:
10
11 *(1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,打印出即可。
12
13 *(2)如果n>k,但n能被k整除,则应打印出k的值,并用n除以k的商,作为新的正整数你n,重复执行第一步。
14
15 *(3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步。
16 *
17 * @param 想想
18 */
19 public static void main(String []args){
20 Scanner s=new Scanner(System.in);
21 System.out.println("键入一个正整数:");
22 int n=s.nextInt();
23 int k=2; //定义一个最小的质因数
24 System.out.print(n+"=");
25 while (k<=n){ //进行循环判断
26 if(k==n){
27 System.out.print(n);
28 break;
29 }else if (n%k==0){
30 System.out.print(k+"*");
31 n=n/k;
32 }else k++;
33 }
34 }
字符串常用方法:
1.int length(); //注意:字符串中是length()方法,数组中是length属性
2.char charAt(int index); //取字符串中索引为index的字符元素
3.String indexOf(String str2); //该方法在str1中检索str2,返回其在str1中第一次出现的位置索引,若找不到则返回-1。
4.String substring(int beginIndex, int endIndex); //截取索引为 [beginIndex,endIndex) 的字符串(左闭右开)
5.char[] toCharArray(); //把字符串转化为字符数组
6.System.arraycopy(原数组,原数组的开始位置,目标数组,目标数组的开始位置,拷贝的元素个数); //(二/6)属于java.lang.System包中。
7.Arrays.sort(nums); //(二/6)关于Arrays类中封装的排序方法(采用归并排序)。
//我在java api中整理了一些常用的,这里出现的数组类型都以int作为示例,当然其他的基本数组类型都是通用的。(见下图)
8.String replace(char oldChar, char newChar); //(一/5)替换字符串中字符或者字符串,char也可以是String
9. String valueOf(T);//各种类型转字符串类型
常用算法:滑动窗口,双指针,DP等
很基础的双指针,要牢牢记住哈~
双指针方法或者直接使用 str1.indexOf(str2),直接用API解决;
public int strStr(String haystack, String needle) {
int L1 = haystack.length();
int L2 = needle.length();
if(L2 == 0) return 0;
int p1 = 0;
while(p1 < L1-L2+1){
while(p1< L1-L2+1 && haystack.charAt(p1) != needle.charAt(0)) p1++;
int curLen = 0, p2 = 0;
while(p1<L1 && p2<L2 && haystack.charAt(p1)==needle.charAt(p2)){
p1++;
p2++;
curLen++;
}
if(curLen == L2) return p1-L2;
//如果不符合,从下一个位置开始
p1 = p1- curLen +1;
}
return -1;
}
反转字符串: 双指针(就像判断回文串的做法)\
public void reverseString(char[] s) {
int left = 0,right = s.length-1;
while(left <= right){
char c = s[left];
s[left] = s[right];
s[right] = c;
left++;
right--;
}
}
541 反转字符串II 给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。
只需要找到每次需要反转的开始和结束的位置,再利用如344的双指针做法即可;
public String reverseStr(String s, int k) {
if(s==null || s.length()==0) return s;
int start = 0;
int len = s.length();
char[] chars = s.toCharArray();
while(start<len){
//如果剩余字符少于 k 个,则将剩余字符全部反转。
if(len-start < k) reverse(chars,start,len-1);
//2k中的前k个
else reverse(chars,start,start+k-1);
//start的下一个位置
start = start+2* k;
}
return new String(chars);
}
public void reverse(char[] s,int start,int end){
while(start <= end){
char c = s[start];
s[start] = s[end];
s[end] = c;
start++;
end--;
}
}
这里的点在于归类,就是abc ,bac怎么是一样的?就把它们转成字符数组,然后,,排序。
字母异位词指字母相同,但排列不同的字符串。
排序数组分类:排序后用map
Map集合中的三种方法:
values():获取集合中的所有的value值;
keySet():将Map中所有的key存放到Set集合中。应为Set集合有迭代器,可以通过迭代器循环key,在通过get()方法,得到每个key所对应的value;
entrySet():Set> entrySet() //返回此映射中包含的映射关系的 Set 视图。 Map.Entry表示映射关系。entrySet():迭代后可以e.getKey(),e.getValue()取key和value。返回的是Entry接口 。
public List<List<String>> groupAnagrams(String[] strs) {
if (strs.length == 0) return new ArrayList();
Map<String, List> ans = new HashMap<String, List>();
for (String s : strs) {
char[] ca = s.toCharArray();
Arrays.sort(ca);
String key = String.valueOf(ca);
//如果没有就先加进key,再加对应的value
if (!ans.containsKey(key)) ans.put(key, new ArrayList());
ans.get(key).add(s);
}
return new ArrayList(ans.values());
}
public int lengthOfLongestSubstring3(String s) {
Map<Character, Integer> map = new HashMap<Character, Integer>();
int left = 0;
int res = 0;
int n = s.length();
for(int i =0;i<n;i++){
if(map.containsKey(s.charAt(i))){
//这里需要注意 abba就可以说明情况,有可能最开始出现的字符直到最后才出现重复的
left = Math.max(map.get(s.charAt(i))+1,left);
}
map.put(s.charAt(i),i);
res = Math.max(res, i-left+1);
}
return res;
}
public String validIPAddress(String IP) {
String[] IP4Arr = IP.split("\\.",-1);
if(IP4Arr.length == 4){
return isIP4Arr(IP4Arr);
}
String[] IP6Arr = IP.split(":",-1);
if(IP6Arr.length == 8){
return isIP6Arr(IP6Arr);
}
return "Neither";
}
public String isIP4Arr(String[] IP4Arr){
for(String ip : IP4Arr){
if(ip.length() > 3 || ip.length() <= 0){
return "Neither";
}
for(int i = 0 ;i < ip.length();++i){
if(!Character.isDigit(ip.charAt(i))){
return "Neither";
}
}
int num = Integer.parseInt(ip);
if(num > 255 || String.valueOf(num).length() != ip.length()){
return "Neither";
}
}
return "IPv4";
}
public String isIP6Arr(String[] IP6Arr){
for(String ip : IP6Arr){
if(ip.length() > 4 || ip.length() <= 0){
return "Neither";
}
for(int i = 0 ;i < ip.length();++i){
char c = ip.charAt(i);
if(!Character.isDigit(c) && !( 'a' <= c && c <= 'f') && !('A' <= c && c <= 'F')){
return "Neither";
}
}
}
return "IPv6";
}
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
滑动窗口(双指针)解决这类问题的思路;
1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
2、什么条件下,窗口应该暂停扩大,开始移动 left 缩小窗口?
3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
public List<Integer> findAnagrams(String s, String p) {
if(s == null || s.length() == 0) return new ArrayList<>();
List<Integer> res = new ArrayList<>();
Map<Character,Integer> dictT = new HashMap<>();
for(int i=0;i<p.length();i++){
int count = dictT.getOrDefault(p.charAt(i), 0);
dictT.put(p.charAt(i),count+1);
}
int required = dictT.size();
//定义两个指针
int L = 0; int R = 0;
//定义一个统计当前窗口的变量
int formed = 0;
Map<Character,Integer> windowCounts = new HashMap<>();
while(R<s.length()){
char c = s.charAt(R);
if(dictT.containsKey(c)){
int value = windowCounts.getOrDefault(c,0);
formed++;
windowCounts.put(c,value+1);
while(formed==required){
//这里来控制连续,不需要去挨个考虑是不是在其中。
//若有不符合条件的,这个不会满足
if(R-L+1 == required){
res.add(L);
}
//窗口向右滑动
char tmp = s.charAt(L);
if(dictT.containsKey(L)){
windowCounts.put(c,windowCounts.get(c)-1);
formed--;
}
L++;
}
R++;
}
}
return res;
}
从后往前填数组即可:
public String replaceSpace(String s) {
//统计修改后的长度
int count = 0;
for(int i = 0;i<s.length();i++){
if(s.charAt(i) != ' ') count++;
else count += 3;
}
char[] chars = new char[count];
count = count-1;
for(int i = s.length()-1;i>=0;i--){
if(s.charAt(i)==' '){
chars[count--] = '0';
chars[count--] = '2';
chars[count--] = '%';
}else{
chars[count--] = s.charAt(i);
}
}
return new String(chars);
}
从后往前找:先把结尾处的空格处理掉,额外要注意的是数组下标要保证有效才可以;
public int lengthOfLastWord(String s) {
if(s==null || s.length()==0) return 0;
int len = s.length();
int index = len-1;
int count = 0;
//先处理掉结尾处的空格
while(index >=0 && s.charAt(index) == ' '){
index--;
}
//从后往前,只需要单独处理一下以空格结尾的即可
while(index >=0){
if(s.charAt(index)==' ') break;
else{
count++;
index--;
}
}
return count;
}
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:
如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。
调用Character类的静态方法:**static boolean isDigit(char c)**可以判断当前字符是否为数字,如果为数字,再使用:int digit = c - ‘0’; 来实现字符转换为数字,从而可以使用:num = num * 10 + digit; 来实现将字符串转换为整数。
int的最大值:Integer.MAX_VALUE
int的最小值:Integer.MIN_VALUE
public int myAtoi(String str) {
if(str==null ||str.length()==0) return 0;
int len = str.length();
int index = 0;
//去掉开头的空格
while(index<len){
if(str.charAt(index) != ' ') break;
index++;
}
//正负号
boolean negtative = false;
if(index<len && str.charAt(index)=='-'){
negtative = true;
index++;
}else if(index<len && str.charAt(index)=='+'){
index++;
}
//组合数字
int num = 0;
while(index <len && Character.isDigit(str.charAt(index))){
int digit = str.charAt(index) -'0';
if(num > (Integer.MAX_VALUE - digit)/10)
return negtative ? Integer.MIN_VALUE:Integer.MAX_VALUE;
//这个是不是很熟悉
else num = num * 10 + digit;
index++;
}
return negtative ? -num:num;
}
个人认为可以参考字符串压缩的思路;
这题可以很好的体现 递归与DP的关系,一个是从上往下,一个是从下往上
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
直接想到了回溯的做法:
回溯说到底是递归,只需要考虑当前位置的情况. 会超时,但递归的思想很重要。因为递归想清楚了,动态规划的状态转移方程也出来了。
public boolean wordBreak(String s, List<String> wordDict) {
return word_Break(s,new HashSet(wordDict),0);
}
public boolean word_Break(String s, HashSet<String> hashSet,int start){
//递归出口
if(start == s.length()) return true;
//递归只需要考虑当前位置的表示就可以了,考虑这个位置之后的每一个单词
//每一步所有的可能,只要有一个满足,就可以返回true
for(int end = start+1;end<=s.length();end++){
if(hashSet.contains(s.substring(start,end)) && word_Break(s,hashSet,end))
return true;
}
return false;
}
动态规划做法:
dp[n+1]: dp[i] 状态 表示子串截止位置为i,那么可以考虑所有小于i的位置j,看前j个和j到i分别是不是字典中的单词。
可以考虑跟上面递归的区别。
public boolean wordBreak(String s, List<String> wordDict) {
HashSet<String> hashSet = new HashSet(wordDict);
//状态, i位置子串是否可拆分
boolean[] dp = new boolean[s.length()+1];
//初始化
dp[0] = true;
//状态
for(int i = 1;i<=s.length();i++){
//可能的划分位置
for(int j = 0;j<i;j++){
if(dp[j] && hashSet.contains(s.substring(j,i))){
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
class Solution {
public int strToInt(String str) {
if(str == null || str.length() == 0) return 0;
int len = str.length();
int cur = 0;
boolean flag = false;
while(cur < len && str.charAt(cur) == ' '){
cur++;
}
if(len == 0 || cur == len) return 0;
int res = 0, bndry = Integer.MAX_VALUE / 10;
if(str.charAt(cur) == '-') flag = true;
if(str.charAt(cur) == '-' || str.charAt(cur) == '+') cur++;
for(int i = cur; i < len; i++){
if(str.charAt(i) < '0' || str.charAt(i) > '9') break;
if(res > bndry || res == bndry && str.charAt(i) > '7')
return flag == false ? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = res * 10 + (str.charAt(i) - '0');
}
return flag == true ? -1*res : res;
}
}
模拟竖式, 字符串加法也在里面了;
从后往前,记录进位,最后翻转;
class Solution {
//模拟竖式
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
String res = "0";
for(int i = num2.length()-1; i >= 0; i--){
//建一个字符串,先补后面的0,再依次按位计算
StringBuilder sb = new StringBuilder();
for(int j = 0; j < num2.length()-1-i; j++){
sb.append(0);
}
//按位算
int m = num2.charAt(i) - '0';
int carry = 0;
for(int j = num1.length()-1 ;j>=0||carry >0 ;j--){
int n = j<0 ? 0: num1.charAt(j) - '0';
int tmp = n * m + carry;
int produce = tmp % 10;
sb.append(produce);
carry = tmp / 10;
}
res = addString(res, sb.reverse().toString());
}
return res;
}
public String addString(String num1, String num2){
StringBuilder sb = new StringBuilder();
int carry = 0;
for(int i = num1.length()-1, j = num2.length()-1; i>=0 || j>=0 || carry>0; i--,j--){
int x = i<0 ? 0: num1.charAt(i) - '0';
int y = j<0 ? 0: num2.charAt(j) - '0';
int tmp = x + y + carry;
sb.append(tmp %10);
carry = tmp /10;
}
return sb.reverse().toString();
}
}
最长公共前缀:数组中所有单词共有的,任意一个单词不包含了,就停了;
public String longestCommonPrefix(String[] strs) {
if(strs==null || strs.length==0) return "";
if(strs.length ==1 ) return strs[0];
String s = strs[0];
int i = 0;
for(i=0;i<s.length();i++){
char c = s.charAt(i);
for(int j = 1;j<strs.length;j++){
if(i==strs[j].length() || strs[j].charAt(i) != c)
return s.substring(0,i);
}
}
return s;
}
给定一个经过编码的字符串,返回它解码后的字符串。
倒是想得到栈的方法,可是写不出来呀~
辅助栈法:
如果当前的字符为数位,解析出一个数字(连续的多个数位)并进栈
如果当前的字符为字母或者左括号,直接进栈
如果当前的字符为右括号,开始出栈,一直到左括号出栈,出栈序列反转后拼接成一个字符串,此时取出栈顶的数字
栈继承自vector,最好不要用,可以像这里一样,用LinkedList或者双端队列(代替)
腾讯笔试题,跟leetcode上稍微有一点点不一样
栈方法
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
LinkedList<String> strs = new LinkedList<>();
LinkedList<Integer> counts =new LinkedList<>();
StringBuilder res = new StringBuilder();
int times = 0;
for(char c: input.toCharArray()){
if(c=='['){
strs.add(res.toString());
res = new StringBuilder();
}else if(c==']'){
//右括号做两件事,一是 :把当前层的串重复几次,二是跟上一层的串在一起
int count = counts.removeLast();
StringBuilder tmp = new StringBuilder();
for(int i=0;i<count;i++) tmp.append(res);
res = new StringBuilder(strs.removeLast() + tmp);
}else if(c=='|'){
counts.add(times);
times = 0;
}else if(Character.isDigit(c)) times = times * 10 + c- '0';
else res.append(c);
}
System.out.println(res.toString());
}
}
import java.util.*;
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
System.out.println(dfs(input,0)[0]);
}
public static String[] dfs(String s,int i){
int times = 0;
StringBuilder res= new StringBuilder();
while(i < s.length()){
char c = s.charAt(i);
if(c=='['){
String[] tmp = dfs(s,i+1);
res.append(tmp[0]);
i = Integer.parseInt(tmp[1]);
}else if(c==']'){
StringBuilder ss =new StringBuilder();
for(int j=0;j<times;j++){
ss.append(res);
}
return new String[]{ss.toString(),i+""};
}else if(Character.isDigit(c)){
times = times* 10+ c-'0';
}else if(Character.isLetter(c)){
res.append(c);
}else{
i++;
continue;
}
i++;
}
return new String[]{res.toString()};
}
}
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。
public String compressString(String S) {
if(S==null|| S.length()==0) return S;
StringBuilder ss = new StringBuilder();
int start = 0;
while(start < S.length()){
int end = start+1;
int count = 1;
ss.append(S.charAt(start));
while(end< S.length() && S.charAt(end)==S.charAt(start)){
count++;
end++;
}
ss.append(count);
start = end;
}
return String.valueOf(ss).length() >= S.length() ? S: String.valueOf(ss);
}
子串
滑动窗口 + hashset
public int lengthOfLongestSubstring(String s) {
if(s==null || s.length()==0) return 0;
int start = 0;
int len = 1;
while(start < s.length()){
int end = start +1;
HashSet<Character> hash = new HashSet<>();
hash.add(s.charAt(start));
while(end < s.length() && !hash.contains(s.charAt(end))){
hash.add(s.charAt(end));
end++;
}
start++;
len = hash.size() > len? hash.size():len;
}
return len;
}
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
这题是一个开始位置就一个窗口,没有收缩
public List<Integer> findSubstring(String s, String[] words) {
if(s==null||s.length()==0||words.length==0) return new ArrayList<Integer>();
List<Integer> res = new ArrayList<>();
HashMap<String, Integer> dict = new HashMap<>();
for(int i = 0; i< words.length;i++){
int num = dict.getOrDefault(words[i],0);
dict.put(words[i],num+1);
}
int len = words[0].length();
int numW = words.length;
int start = 0;
int required = dict.size();
while(start < s.length()- numW*len+1){
HashMap<String, Integer> window = new HashMap<>();
//因为单词是固定长度的,所以每次取固定长度的子串作为单词考虑
int count = 0;
while(count < numW){
String word = s.substring(start+ count*len,start+(count+1)*len);
if(dict.containsKey(word)){
window.put(word,window.getOrDefault(word,0)+1);
if(window.get(word) > dict.get(word)){
break;
}
count++;
}else{
break;
}
}
if(count == numW){
res.add(start);
}
start++;
}
return res;
}
}
这题是只有一个窗口,夸张和收缩
还是滑动窗口,但是有窗口的扩和缩。跟上面的438很像
public String minWindow(String s, String t) {
if(s==null ||s.length() < t.length()) return "";
Map<Character,Integer> dictT = new HashMap<>();
for(int i = 0;i<t.length();i++){
int num = dictT.getOrDefault(t.charAt(i),0);
dictT.put(t.charAt(i), num+1);
}
int start = 0, end = 0;
int required = dictT.size();//有多少个不同的字符
int formed = 0; //已经满足要求的字符的个数
int[] res = {-1,0,0};
Map<Character,Integer> cur = new HashMap<>();//存的是当前窗口各个字符及其数目
while(end<s.length()){
char c = s.charAt(end);
int n = cur.getOrDefault(c,0);
cur.put(c,n+1);
//这里有个integer的点,超过-128到127的话,integer的值是以对象的形式存在堆中的,所以不能直接比较
if(dictT.containsKey(c) && dictT.get(c).intValue() == cur.get(c).intValue()) formed++;
//缩
while(formed == required && start<=end) {
c = s.charAt(start);
//更新
if(res[0] == -1 || end-start < res[2]-res[1]){
res[0] = end-start+1;
res[1] = start;
res[2] = end;
}
//更新当前窗口对应的map和formed
cur.put(c,cur.get(c)-1);
if(dictT.containsKey(c) && cur.get(c) <dictT.get(c)){
formed--;
}
start++;
}
//扩
end++;
}
return res[0]==-1? "":s.substring(res[1],res[2]+1);
}
回文一定要注意,处理的时候,一定要分奇偶
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。
解法一:从回文的定义出发,找回文中心
public int countSubstrings(String s) {
if(s == null || s.equals("")){
return 0;
}
int res = 0;
int n = 2*s.length()-1;
//错误做法,没考虑偶数的情况,空格也有可能称为回文中心
for(int i = 0; i<n; i++){
//先让left和right指向同一个,或相邻的
int left = i/2;
int right = left+i%2;
while(left>=0 && right< s.length() && s.charAt(left)==s.charAt(right)){
res++;
left--;
right++;
}
}
return res;
}
解法二: 详见 DP-字符串DP
public int countSubstrings(String s) {
if(s==null||s.length()==0) return 0;
boolean[][] dp = new boolean[s.length()][s.length()];
//初始化
for(int i =0;i<s.length();i++){
dp[i][i] = true;
}
int res = s.length();
//dp,只需要填上三角矩阵即可
for(int i = s.length()-1;i>=0;i--){
for(int j = i+1;j<s.length();j++){
if(s.charAt(i)==s.charAt(j)){
if(j-i == 1) dp[i][j] = true;//偶数的情况
else dp[i][j] = dp[i+1][j-1];
}else{
dp[i][j] =false;
}
if(dp[i][j]) res++;
}
}
return res;
}
public String longestPalindrome(String s) {
if(s==null||s.length()==0) return s;
int n = s.length();
boolean[][] dp = new boolean[n][n];
int[] res = {-1,0,0};
for(int i = 0;i<n;i++) dp[i][i] = true;
for(int i=n-1;i>=0;i--){
for(int j= i+1;j<n;j++){
if(s.charAt(i)==s.charAt(j)){
if(j-i==1) dp[i][j] = true;
else dp[i][j] = dp[i+1][j-1];
}else{
dp[i][j] = false;
}
if(dp[i][j]==true && j-i+1 > res[0]){
res[0] = j-i+1;
res[1] = i;
res[2] = j;
}
}
}
return s.substring(res[1],res[2]+1);
}
在代码中,我们用 ans 存储回文串的长度,由于在遍历字符时,ans 每次会增加 v / 2 * 2,因此 ans 一直为偶数。但在发现了第一个出现次数为奇数的字符后,我们将 ans 增加 1,这样 ans 变为奇数,在后面发现其它出现奇数次的字符时,我们就不改变 ans 的值了。
class Solution {
public int longestPalindrome(String s) {
int[] count = new int[128];
int length = s.length();
for (int i = 0; i < length; ++i) {
char c = s.charAt(i);
count[c]++;
}
int ans = 0;
for (int v: count) {
ans += v / 2 * 2;
if (v % 2 == 1 && ans % 2 == 0) {
ans++;
}
}
return ans;
}
}
子序列
public int longestCommonSubsequence(String text1, String text2) {
if(text1==null||text2==null||text1.length()==0||text2.length()==0) return 0;
int len1= text1.length();
int len2 = text2.length();
if(len2> len1) return longestCommonSubsequence(text2,text1);
int[][] dp = new int [len2][len1];
//初始化
if(text1.charAt(0)==text2.charAt(0)) dp[0][0] = 1;
else dp[0][0] = 0;
//第一行
for(int i = 1;i<len1;i++){
if(text1.charAt(i) == text2.charAt(0)) dp[0][i] = 1;
else dp[0][i] = dp[0][i-1];
}
//第一列
for(int j = 1;j< len2;j++){
if(text1.charAt(0) == text2.charAt(j)) dp[j][0] = 1;
else dp[j][0] = dp[j-1][0];
}
//dp
for(int i = 1;i<len2;i++){
for(int j = 1;j<len1;j++){
if(text1.charAt(j)==text2.charAt(i)){
dp[i][j] = dp[i-1][j-1] +1;
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[len2-1][len1-1];
}
public int lengthOfLIS(int[] nums) {
if(nums.length== 0) return 0;
//状态:以i为结尾的子序列的长度
int n = nums.length;
int[] dp = new int [n];
//初始化
Arrays.fill(dp,1);
for(int i = 1;i<n;i++){
for(int j= 0;j<i;j++){
if(nums[i]> nums[j]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
}
int res = 0;
for(int i =0;i<n;i++){
res = Math.max(res,dp[i]);
}
return res;
}
public int longestPalindromeSubseq(String s) {
if(s==null || s.length() ==0) return 0;
int n = s.length();
//从i到j位置的子序列长度
int[][] dp = new int[n][n];
//初始化
for(int i = 0;i<n;i++){
dp[i][i] = 1;
}
//dp
for(int i = n-1;i>=0;i--){
for(int j = i+1;j<n;j++){
if(s.charAt(i)==s.charAt(j)){
dp[i][j] = dp[i+1][j-1]+2;
}else{
dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args)throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s1 = br.readLine();
String s2 = br.readLine();
int res = getRes(s1,s2);
System.out.println(res);
}
public static int getRes(String s1,String s2){
if(s1==null&&s2==null) return 0;
if(s1==null||s2==null) return 0;
if(s1.length()==0||s2.length()==0||s1.length()<s2.length()) return 0;
char[] arr = s1.toCharArray();
char[] p = s2.toCharArray();
int[] map = new int[256];
for(int i=0;i<p.length;i++){
map[p[i]]++;
}
int left = 0;
int right = 0;
int minLen = Integer.MAX_VALUE;
int match = p.length;
while(right!=arr.length){
map[arr[right]]--;
if(map[arr[right]]>=0){
match--;
}
if(match==0){
while(map[arr[left]]<0){
map[arr[left]]++;
left++;
}
minLen = Math.min(right-left+1,minLen);
match++;
map[arr[left++]]++;
}
right++;
}
return minLen==Integer.MAX_VALUE?0:minLen;
}
}
//这题属于匹配括号的类型
class Solution {
//栈
//左括号就入栈,右括号就要出栈看是不是对应的左(怎么看是不是对应的左?用个map)
public boolean isValid(String s) {
if(s==null) return true;
int index = 0;
LinkedList<Character> dict = new LinkedList<>();
HashMap<Character,Character> map = new HashMap<>(){{
put(')','(');
put(']','[');
put('}','{');
}};
while(index < s.length()){
char c = s.charAt(index);
if(c=='(' || c=='{' || c=='[') dict.add(c);
else{//如果按这种方法写,挨个匹配左右是不是很烦,怎么可以直接确定它们的对应关系
if(dict.size()==0) return false;
char cc = dict.removeLast();
if(cc != map.get(c)) return false;
}
index++;
}
return dict.size()==0? true: false;
}
}
//这里只有一种 括号 ,在每一次只能有两种选择。,只需要控制右的数量不大于左,左的数量不大于n
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
StringBuilder s = new StringBuilder();
dfs(s,0,0,n);
return res;
}
public void dfs(StringBuilder cur, int left, int right, int n){
if(right >left || left > n){
return ;
}
if(cur.length() == 2*n){
res.add(cur.toString());
return ;
}
//像搜索地图可以上下左右,这里只能两种
cur.append("(");
dfs(cur,left+1,right,n);
cur.deleteCharAt(cur.length()-1);
cur.append(")");
dfs(cur,left,right+1, n);
cur.deleteCharAt(cur.length()-1);
}
}
class Solution {
//用栈的思想
//有栈是不是就可以递归
//递归的 优化 栈
public int longestValidParentheses(String s) {
if(s==null||s.length()==0) return 0;
int max = 0;
int[] dp = new int[s.length()+1];
s = " "+s;
for(int i =2; i< s.length(); i++){
if(s.charAt(i) ==')'){
if(s.charAt(i-1) == '('){
dp[i] = dp[i-2] +2;
}else if (s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2];
}
max = Math.max(max, dp[i]);
}
}
return max;
}
}
import java.util.Stack;
/*
* 使用两个栈实现一个队列
*/
public class StackToQueue {
private Stack<Integer> stackPush;
private Stack<Integer> stackPoll;
//构造,初始化栈
public StackToQueue() {
stackPush = new Stack<Integer>();
stackPoll = new Stack<Integer>();
}
/*
* 因为是一次性把push的元素全倒在poll里面
* 这里要重点理解一下!!!!!!!!!!!!!!!!!!
*/
public void push(int num)
{
stackPush.push(num);
}
public int poll() {
if(stackPoll.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empety");
}
else if(stackPoll.empty()) {
while(!stackPush.empty()) {
stackPoll.push(stackPush.pop());
}
}
return stackPoll.pop();
}
public int peek() {
if(stackPush.empty() && stackPoll.empty()) {
throw new RuntimeException("Queue is empety");
}else if(stackPoll.empty()) {
while(!stackPush.empty()) {
stackPoll.push(stackPush.pop());
}
}
return stackPoll.peek();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
StackToQueue sq = new StackToQueue();
sq.push(5);
Integer num = sq.peek();
System.out.println(num);
}
有点像两个杯子挪乒乓球,每次都只留杯子底的那个,把前面的倒进另一个杯子。
public class QueueToStack {
private Queue<Integer> data;
private Queue<Integer> help;
public QueueToStack() {
data = new LinkedList<Integer>();
help = new LinkedList<Integer>();
}
public void push(int num) {
data.add(num);
}
//integer ? int
public int pop() {
if(data.isEmpty()) {
throw new RuntimeException("stack is empety");
}while(data.size()>1) {
help.add(data.poll());
}
int num = data.poll();
swap();
return num;
}
public int peek() {
if(data.isEmpty()) {
throw new RuntimeException("stack is empety");
}while(data.size()>1) {
help.add(data.poll());
}
int num = data.poll();
help.add(num);
swap();
return num;
}
private void swap() {
Queue<Integer> tmp = help;
help = data;
data = tmp;
}
}
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
写了八百年一直错错错,加个哨兵就那么简单!!!!!!!!!!!!!!!!!!!!!
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if (len == 0) {
return 0;
}
if (len == 1) {
return heights[0];
}
int res = 0;
//加两个 哨兵
int[] newHeights = new int[len + 2];
newHeights[0] = 0;
System.arraycopy(heights, 0, newHeights, 1, len);
newHeights[len + 1] = 0;
len += 2;
heights = newHeights;
Deque<Integer> stack = new ArrayDeque<>(len);
// 先放入哨兵,在循环里就不用做非空判断
stack.addLast(0);
for (int i = 1; i < len; i++) {
while (heights[i] < heights[stack.peekLast()]) {
int curHeight = heights[stack.pollLast()];
int curWidth = i - stack.peekLast() - 1;
res = Math.max(res, curHeight * curWidth);
}
stack.addLast(i);
}
return res;
}