[leetcode02]


记录leetcode刷题的笔记,参考了leetcode的高赞答案和官方解答.

二进制求和leetcode67

给定两个二进制字符串,返回他们的和(用二进制表示)。

输入为非空字符串且只包含数字 1 和 0。

示例 1:

输入: a = “11”, b = “1”
输出: “100”
示例 2:

输入: a = “1010”, b = “1011”
输出: “10101”

思路:

  1. 将两个字符串进行比较,将两个字符串扩展为统一长度.这个过程不能使用string.format函数,否则在转化为Integer类型时会发生越界错误.所以可以利用建立一个新的字符串来解决
  2. 从字符串的尾部开始遍历,建立两个变量,t,c.c表示上一位是否有进位,如果没有则是0,否则为1.t用来记录同位置的两数相加(如果c为1也要加上去)
  3. StringBuffer最后append的结果为(char)(t%2+‘0’).
public String addBinary(String str1, String str2) {
        int length = Math.max(str1.length(), str2.length());
        String extendedStr1 = "";
        //将两个字符串转化为统一长度
        if (str1.length() == str2.length()) {

        } else if (str1.length() < str2.length()) {
            for (int i = str1.length(); i < length; i++) {
                extendedStr1 += "0";
            }
            extendedStr1 += str1;
            str1 = extendedStr1;
        } else {
            for (int i = str2.length(); i < length; i++) {
                extendedStr1 += "0";
            }
            extendedStr1 += str2;
            str2 = extendedStr1;
        }
        StringBuffer ans = new StringBuffer();
        int t = 0, c = 0;
        //进行二进制运算
        for (int i = length - 1; i >= 0; i--) {
            t += str1.charAt(i) - '0';
            t += str2.charAt(i) - '0';
            if (c == 0) {
                if (t > 1) {
                    c = 1;
                    ans.append((char) (t % 2 + '0'));
                } else {
                    ans.append((char) (t % 2 + '0'));
                }
            } else {
                t += c;
                if (t > 1) {
                    ans.append((char) (t % 2 + '0'));
                } else {
                    c = 0;
                    ans.append((char) (t % 2 + '0'));
                }
            }
            t = 0;
        }
        if (c == 1) {
            ans.append('1');
        }
        //返回结果
        return ans.reverse().toString();
    }

移除元素leetcode27

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。
示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。

思路:双指针法,

  1. 建立两个指针,left,right.
  2. 遍历数组,如果nums[right]!=val,则将nums[left]=nums[right],left++,right++.
  3. 最后返回left.
    public int removeElement(int[] nums, int val) {
        int n = nums.length;
        int left = 0;
        for (int right = 0; right < n; right++) {
            if (nums[right] != val) {
                nums[left] = nums[right];
                left++;
            }
        }
        return left;
    }

删除排序数组中的重复项leetcode26

给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2],

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。

你不需要考虑数组中超出新长度后面的元素。
示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。

思路:双指针法,

  1. 最后返回left+1.
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        int left = 0;
        for (int right = 1; right < n; right++) {
            if (nums[left] != nums[right]) {
                left++;
                nums[left] = nums[right];
            }
        }
        return left + 1;
    }

删除排序数组中的重复项IIleetcode80

给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定 nums = [1,1,1,2,2,3],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 1, 1, 2, 2, 3

你不需要考虑数组中超出新长度后面的元素。
示例 2:

给定 nums = [0,0,1,1,1,1,2,3,3],

函数应该返回新的长度 7, 并且原数组 nums 的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3

你不需要考虑数组中超出新长度后面的元素。

思路:双指针法


88. 合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。

你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

输出: [1,2,2,3,5,6]

思路:双指针法,需要注意的是,我们无序创建新的数组:由于nums1的长度为m+n,nums2的长度为n,所以我们可以直接在nums1上进行操作,将nums2合并到nums1上.可以指针设置为从后向前遍历,每次取两者之中的较大者放进 nums1的最后面。

public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i=m+n-1,a=m-1,b=n-1;
        while(a>-1||b>-1){
            if(a==-1){
                nums1[i]=nums2[b];
                b--;i--;
            }else if(b==-1){
                nums1[i]=nums1[a];
                a--;i--;
            }else if(nums1[a]>=nums2[b]){
                nums1[i]=nums1[a];
                a--;i--;
            }else{
                nums1[i]=nums2[b];
                b--;i--;
            }
        }
    }

4. 寻找两个正序数组的中位数

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。

请你找出这两个正序数组的 中位数 ,并且要求算法的时间复杂度为 O(log(m + n)) 。

你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0
示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

思路:切割法

    public double findMedianSortedArrays(int[] A, int[] B) {
        int m = A.length;
        int n = B.length;
        if (m > n) {
            return findMedianSortedArrays(B, A); // 保证 m <= n
        }
        int min = 0;
        int max = m;
        while (min <= max) {
            int i = (min + max) / 2;
            int j = (m + n + 1) / 2 - i;
            if (i != 0 && j != n && A[i - 1] > B[j]) {//i需要减小
                max = i - 1;
            } else if (j != 0 && i != m && B[j - 1] > A[i]) {//i需要增大
                min = i + 1;
            } else {
                int ml = 0;
                if (i == 0) {
                    ml = B[j - 1];
                } else if (j == 0) {
                    ml = A[i - 1];
                } else {
                    ml = Math.max(A[i - 1], B[j - 1]);
                }
                if ((m + n) % 2 == 1) return ml;//如果是奇数,就不用考虑切割的右边
                int mr = 0;
                if (j == n) {
                    mr = A[i];
                } else if (i == m) {
                    mr = B[j];
                } else {
                    mr = Math.min(A[i], B[j]);
                }
                return (ml + mr) / 2.0;//如果是偶数就返回结果
            }
        }
        return 0.0;
    }

382.链表随机节点

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。

思路:使用一个数组来存储链表的节点值,然后使用随机数来随机选择数组中的一个元素.

复杂度分析:

  • 时间复杂度:初始化为 O ( n ) O(n) O(n),随机选择为 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( n ) O(n) O(n)
List<Integer> list = new ArrayList<Integer>();
Random random = new Random();

public Solution(ListNode head) {
    while (head != null) {
        list.add(head.val);
        head = head.next;
    }
}

public int getRandom() {
    return list.get(random.nextInt(list.size()));
}

思路:水塘抽样

从链表头开始,遍历整个链表,对遍历到的第 i 个节点,随机选择区间 [0,i) 内的一个整数,如果其等于 0,则将答案置为该节点值,否则答案不变。

该算法会保证每个节点的成为最后被返回的值的概率均为 1 n \frac{1}{n} n1,证明如下:
P ( 第 i 个节点的值成为最后被返回的值 ) = P ( 第 i 随机选择的值为0 ) × P ( 第 i + 1 次随机选择的值不为0 × ⋯ × P ( 第 n 次随机选择的值不为0 ) ) = 1 i × i − 1 i + 1 × ⋯ × n − i + 1 n = 1 n \begin{aligned} &P(\text{第}i\text{个节点的值成为最后被返回的值})\\&= P(\text{第}i\text{随机选择的值为0})\times P(\text{第}i+1\text{次随机选择的值不为0}\times\cdots\times P(\text{第}n\text{次随机选择的值不为0}))\\&= \frac{1}{i}\times\frac{i-1}{i+1}\times\cdots\times\frac{n-i+1}{n}\\&= \frac{1}{n} \end{aligned} P(i个节点的值成为最后被返回的值)=P(i随机选择的值为0)×P(i+1次随机选择的值不为0××P(n次随机选择的值不为0))=i1×i+1i1××nni+1=n1

class Solution {
    ListNode head;
    Random random;

    public Solution(ListNode head) {
        this.head = head;
        random = new Random();
    }

    public int getRandom() {
        int i = 1, ans = 0;
        for (ListNode node = head; node != null; node = node.next) {
            if (random.nextInt(i) == 0) { // 1/i 的概率选中(替换为答案)
                ans = node.val;
            }
            ++i;
        }
        return ans;
    }
}

203.移除链表元素

删除链表中等于给定值 val 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

思路:由于head节点本身可能会被删除,所以要指定一个node节点,它的next节点还head,再通过temp节点来遍历整个链表.删除相应的节点即可.

public ListNode removeElements(ListNode head, int val) {
       ListNode node =  new ListNode(0);
       node.next=head;
       ListNode temp = node;
        while(temp.next!=null){
            if(temp.next.val==val){//删除
                temp.next=temp.next.next;
            }else{
                temp = temp.next;
            }
        }
        return node.next;
}

237.删除链表中的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。

思路:由于要删除的是node节点,所以考虑将node.next复制到node上,然后删除node.next即可.

public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
}

328.奇偶链表

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
在这里插入图片描述

  • 思路:使用两个链表分别存储奇数和偶数节点,然后将奇数链表的最后一个节点指向偶数链表的头节点即可.
public ListNode oddEvenList(ListNode head) {
        if (head == null) return null;
        ListNode odd = head;//奇数链表
        ListNode even = odd.next;//偶数链表
        ListNode temp = even;//偶数头节点
        while (odd.next != null && odd.next.next != null) {
            odd.next = odd.next.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = temp;
        return head;
}

86.分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

public ListNode partition(ListNode head, int x) {
    ListNode dummy = new ListNode(-1);dummy.next=head;
    ListNode p = head;//head记录的是小于x最右端的节点位置
    //pre记录p之前的节点,p遍历所有节点
    ListNode pre = dummy;//刚开始时,dummy与pre是同一个,p与head是同一个
    head = dummy;
    while (p!=null) {
        if (p.val<x) {
            pre.next = p.next;//这一步和下一步不能调换,否则由于p和head一开始相同,会出现循环指的问题
            p.next = head.next;
            head.next = p;//第一次循环时,这样得到的结果就是:链表结构不改变
            // 往后每次循环时,这样得到的结果就是:p指的节点(小于x的节点)会被插入到head的next位置
            // 并且不会改变原有的结构.
            head = p;
            pre = p;
            p = pre.next;//循环一次之后,pre和head是同一个,p在head和pre的next位置
            //循环之后,pre和head变成了p所在的位置,而p处于pre.next位置,继续下一次的循环
        } else {
            p = p.next;
            pre = pre.next;
        }
    }
    return dummy.next;
}

24.两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

输入:head = [1,2,3,4]
输出:[2,1,4,3]

public ListNode swapPairs(ListNode head) {
    ListNode dummyHead = new ListNode(0);
    dummyHead.next = head;
    ListNode temp=dummyHead;
    while(temp.next!=null&&temp.next.next!=null){
        ListNode node1 = temp.next;
        ListNode node2 = temp.next.next;
        temp.next = node2;
        node1.next=node2.next;
        node2.next = node1;
        temp=node1;
    }
    return dummyHead.next;
}

876.链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

  • 思路:快慢指针法(注意跳出循环的条件)
    public ListNode middleNode(ListNode head) {
        if(head==null||head.next==null){
            return head;
        }
        ListNode p = head.next;
        ListNode q = head.next.next;
        while(q!=null&&q.next!=null){
            p=p.next;
            q=q.next.next;

        }
        return p;
    }

234.回文链表

请判断一个链表是否为回文链表。

思路:看注释,注意while的条件,注意prepre的初始值为null,注意判断奇偶性的fast可能取值,

public boolean isPalindrome(ListNode head) {
        //如果头为空或者链表就一个数,则为true
        if (head == null || head.next == null) {
            return true;
        }
        //slow慢指针,fast快指针,fast遍历到尾巴时,slow刚好到中间
        //pre和prepre作为反转前半链表的操作指针
        //pre紧贴slow,而prepre作为pre的next,即原链表中pre的前一个节点
        ListNode slow = head, fast = head;
        ListNode pre = head, prepre = null;
        //当fast节点遍历到尾巴或者遍历到尾巴的下一个(null)跳出
        while (fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
            //开始反转前半链表
            //先让在前的pre改变原指向后的链条,而去指向prepre,即pre的前一个节点,即反转
            //再让prepre在新的“前端”等待下一次反转
            pre.next = prepre;
            prepre = pre;
        }
        //当fast!=null,表示链表为奇数个,此时slow在中间,需要把slow往后移动一个到后半对称的开始
        if (fast != null) {
            slow = slow.next;
        }
        //此时,slow为后半链条的头,pre为前半链条的头,开始循环比较即可
        while (slow != null && pre != null) {
            if (slow.val != pre.val) {
                return false;
            }
            slow = slow.next;
            pre = pre.next;
        }
        return true;
    }

143.重排链表

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

  • 注意到目标链表为原链表的左半端和反转后的右半端合并后的结果,所以任务分为三步
    • 1.找到链表的中点,利用快慢指针实现
    • 2.将链表的后半段反转
    • 3.将前后两部分合并
public void reorderList(ListNode head) {
    if (head == null || head.next == null) {
        return;
    }
    ListNode middle = middle(head);
    ListNode l1 = head;
    ListNode l2 = middle.next;
    middle.next = null;
    l2 = reverse(l2);
    merge(l1, l2);
}

public ListNode middle(ListNode head) {
    ListNode slow = head;
    ListNode fast = head;
    while (fast.next != null && fast.next.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

public ListNode reverse(ListNode head) {
    ListNode pre = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode nextTemp = cur.next;
        cur.next = pre;
        pre = cur;
        cur = nextTemp;
    }
    return pre;
}

public void merge(ListNode l1, ListNode l2) {
    ListNode l1_tem;
    ListNode l2_tem;
    while (l1 != null && l2 != null) {
        l1_tem = l1.next;
        l2_tem = l2.next;
        l1.next = l2;

        l1 = l1_tem;
        l2.next = l1_tem;
        l2 = l2_tem;
    }
}
  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值