算法入门
第1天 二分查找
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = (left + right) >> 1;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return -1;
}
}
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
if(n == 1){
return 1;
}
int left = 1;
int right = n;
while(left < right){
int mid = left + (right - left) / 2;
if(!isBadVersion(mid)){
left = mid + 1;
}else{
right = mid;
}
}
return right;
}
}
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
if(target < nums[left]){
return left;
}
if(target > nums[right]){
return right + 1;
}
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid;
}
}
return right;
}
}
第2天 双指针
利用一个新数组来维护结果,利用双指针的方式,从原数组两端找出绝对值较大的数放到新数组的末尾,直至遍历完整数数组,最后将数组的值变为原来的平方
class Solution {
public int[] sortedSquares(int[] nums) {
int left = 0;
int right = nums.length - 1;
int [] res = new int[nums.length];
for(int i = 0; i < nums.length && left <= right; i++){
res[nums.length - 1 - i] = Math.abs(nums[left]) > Math.abs(nums[right]) ?
Math.abs(nums[left++]) : Math.abs(nums[right--]);
}
square(res);
return res;
}
public void square(int [] nums){
for(int i = 0; i < nums.length; i++){
nums[i] = (int)Math.pow(nums[i], 2);
}
}
}
整体翻转 + 局部翻转
class Solution {
public void rotate(int[] nums, int k) {
k = k % nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
public void reverse(int [] nums, int start, int end){
while(start < end){
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
第3天 双指针
利用双指针从左向右遍历,将非零数按照出现的顺序排序,最后所有零都自然的排到了数组的末尾
class Solution {
public void moveZeroes(int[] nums) {
int left = 0;
int right = 0;
if(nums.length == 1){
return ;
}
while(right < nums.length){
if(nums[right] != 0){
int temp = nums[right];
nums[right] = nums[left];
nums[left] = temp;
left++;
}
right++;
}
}
}
可以用两数之和的哈希表解法,牺牲时间换内存 因为数组是有序的,所以利用双指针来解决更方便
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while(left < right){
int sum = numbers[left] + numbers[right];
if(sum == target){
return new int []{++left, ++right};
}else if(sum > target){
right--;
}else{
left++;
}
}
return new int[]{-1, -1};
}
}
第4天 双指针
class Solution {
public void reverseString(char[] s) {
if(s.length == 1){
return ;
}
int start = 0;
int end = s.length - 1;
while(start < end){
char temp = s[start];
s[start] = s[end];
s[end] = temp;
start++;
end--;
}
}
}
遍历字符串,通过一个指针来找到所有片段,从后向前添加每个片段的字符,根据指针的位置添加空格【此处采用的StringBuilder是线程不安全的】
class Solution {
public String reverseWords(String s) {
if(s.length() == 1){
return s;
}
StringBuilder sb = new StringBuilder();
int i = 0;
while(i < s.length()){
int start = i;
while(i < s.length() && s.charAt(i) != ' '){
i++;
}
for(int j = start; j < i; j++){
sb.append(s.charAt(start + i - 1 - j));
}
if(i < s.length() && s.charAt(i) == ' '){
i++;
sb.append(" ");
}
}
return sb.toString();
}
}
解法2: 下面是通过分割获取每个片段的长度
class Solution {
public String reverseWords(String s) {
if(s.length() == 1){
return s;
}
StringBuffer sb = new StringBuffer();
String [] str = s.split(" ");
int index = 0;
for(int i = 0; i < s.length(); ){
int len = str[index].length();
for(int j = 0; j < len; j++){
sb.append(s.charAt(i + len - 1 - j));
}
if(index < str.length - 1 ){
sb.append(" ");
}
i = i + len + 1;
index++;
}
return sb.toString();
}
}
第5天 双指针
可以通过 快慢指针 来完成,快指针每次走两步,慢指针每次走一步,当快指针走到尽头,慢指针就到达了链表的中点。
重点在于临界条件的处理,快指针最后有两种状态:
指向链表的最后一个元素【链表元素为奇数个】 指向空 【链表元素为偶数个】
所以临界条件应该为 fast != null && fast.next != null
class Solution {
public ListNode middleNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
遍历一次链表,统计链表的结点个数,判断是否要删除第一个结点,是则直接返回链表的第二个结点,否则就遍历寻找待删除结点的前驱结点,删除后返回链表头结点
要注意,此题链表的头结点指的是链表的第一个结点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
int count = 0;
ListNode p = head;
while(p != null){
count++;
p = p.next;
}
if(count == n){
return head.next;
}
ListNode pre = head;
for(int i = 0; i < count - n - 1; i++){
pre = pre.next;
}
pre.next = pre.next.next;
return head;
}
}
第6天 滑动窗口
利用 HashMap 来存储字符串中的元素与出现的位置,每遍历一个元素,就将其添加到哈希表中,Key 为字符 —— Value 为元素当前的位置。
利用滑动的窗口来记录不重复子串的长度,left 定义为左边界。 当哈希表中存在了当前的字符,那么就更新左边界的位置,此时分为两种情况:
left = Math.max(left, map.get(ch) + 1);
(1)找到上次出现该字符的位置,将左边界移动到该位置的右边 (2)可能移动后的位置在当前左边界的左边,如果这样就不更新左边界
记录最大无重复子串的长度maxLen = Math.max(maxLen, i - left + 1);
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.length() == 0){
return 0;
}
Map<Character, Integer> map = new HashMap<Character, Integer>();
int left = 0;
int maxLen = 0;
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);
if(map.containsKey(ch)){
left = Math.max(left, map.get(ch) + 1);
}
map.put(ch, i);
maxLen = Math.max(maxLen, i - left + 1);
}
return maxLen;
}
}
- 滑动窗口的大小就是 s1 的大小
- 遍历字符串,在 s2 中截取和窗口长度相同大小的子串
- 调用方法通过两个字符串的组成是否相同来比较。【固定窗口大小】
class Solution {
public boolean checkInclusion(String s1, String s2) {
int left = 0;
int right = s1.length();
boolean res = false;
while(right <= s2.length()){
res = isSame(s1, s2.substring(left, right));
if(res){
break;
}
left++;
right++;
}
return res;
}
public boolean isSame(String s1, String s2){
int [] num = new int[26];
for(int i = 0; i < s2.length(); i++){
char c = s2.charAt(i);
num[c - 'a']++;
}
for(int j = 0; j < s1.length(); j++){
char ch = s1.charAt(j);
if(--num[ch - 'a'] < 0){
return false;
}
}
return true;
}
}
九月打卡
9月1日
class Solution {
public int[] finalPrices(int[] prices) {
for(int i = 0; i < prices.length; i++){
for(int j = i + 1; j < prices.length; j++){
if(prices[j] <= prices[i]){
prices[i] -= prices[j];
break;
}
}
}
return prices;
}
}
9月2日
利用 深度优先搜索,自底向上 的思想,记录当前结点左、右结点的最长同值路径。当前结点的最长同值路径是其左右结点的最长同值路径的和。利用全局变量来记录结果。
注意:dfs返回的值应该是左子树或右子树的最长路径,因为一条路径不会出现分叉。
class Solution {
int res = 0;
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return res;
}
public int dfs(TreeNode root){
if(root == null){
return 0;
}
int left = dfs(root.left);
int right = dfs(root.right);
if(root.left != null && root.left.val == root.val){
left++;
}else{
left = 0;
}
if(root.right != null && root.right.val == root.val){
right++;
}else{
right = 0;
}
res = Math.max(res, left + right);
return Math.max(left, right);
}
}
9月3日
- 先根据每行的第一个元素进行排序【使用Arrays的sort方法、自己设计一个排序方法】
(1)方法一:
Arrays.sort(pairs, (a, b) -> a[0] - b[0]);
这个方法实际后面的部分是 一个实现接口的内部类,写成了Lambda表达式 如果换为 b[0] - a[0] ,则是逆序排序 如果将 0 换为 1 ,则是根据每行的第二个与元素进行排序
(2)方法二:【采用的是冒泡排序】
for(int i = 0; i < pairs.length - 1; i++){
for (int j = 0; j < pairs.length - 1 - i; j++) {
if(pairs[j][0] > pairs[j + 1][0]){
int [] temp = pairs[j];
pairs[j] = pairs[j + 1];
pairs[j + 1] = temp;
}
}
}
- 利用动态规划,设置 dp 数组 【dp[i] 代表以第pairs[i] 结尾时的最大对数链】
- 对数链至少有一个,所以将数组初始化为1
Arrays.fill(dp, 1);
dp[i] = Math.max(dp[i], dp[j] + 1)
class Solution {
public int findLongestChain(int[][] pairs) {
Arrays.sort(pairs, (a,b) -> a[0] - b[0]);
int n = pairs.length;
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (pairs[i][0] > pairs[j][1]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
return dp[n - 1];
}
}
|