一、出现一次的数
题目:
/** * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 * 说明: * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? * 示例 1: * 输入: [2,2,1] * 输出: 1 * 示例 2: * 输入: [4,1,2,1,2] * 输出: 4 */
看完题便想到了,双重for循环,找到出现两次的数就跳出内层循环,没找到就返回该数
这里用到了flag,这种编程方式虽然好用,但是大量使用容易造成代码可读性下降,所以不建议使用
public static int method1(int[] nums) {
flag1:for (int i=0;i<nums.length;i++){
for (int j=0;j<nums.length;j++){
if (nums[i]==nums[j]&&i!=j){
continue flag1;
}
}
return nums[i];
}
return nums[nums.length-1];
}
第二种思路:任何一个数异或自己两次等于自身
比如:a=10,a^=5,a^=5
最后a还是等于10
所以可以遍历每一个数,用0异或所有的数,因为0异或任何数都等于对方
public static int method2(int[] nums){
int a=0;
for(int i:nums){
a^=i;
}
return a;
}
很简洁
二、成环的链表
题目:
/** * 给定一个链表,判断链表中是否有环。 * 链表中数字各不相同 */class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } }
可以根据链表中数字各不相同入手,使用set,一旦碰到数字在set中出现过就返回false,走完就返回true
public boolean method1(ListNode head) {
Set<ListNode>node = new HashSet<>();
while(head!=null)
{
if(node.contains(head))
return true;
else
node.add(head);
head = head.next;
}
return false;
}
可以从环的特征入手,在一个环形赛道上跑步,跑的快的必定可以超过慢的一圈,使用快慢指针
public boolean method2(ListNode head){
if (head == null || head.next == null) {
return false;
}
ListNode fast = head.next;
ListNode slow = head;
while(fast != slow){
//如果快指针把整个赛道都跑完了,跑到了null,那说明这个赛道是有尽头的。
if (fast == null || fast.next == null) {
return false;
}
fast = fast.next.next;
slow = slow.next;
}
return true;
}
可以使用一种标记,在走过的地方留下标记,如果遇到了之前留下的标记,说明自己跑了一个圈
public boolean method3(ListNode node){
while (node!=null){
if (node.val==Integer.MIN_VALUE){
return true;
}else {
node.val=Integer.MIN_VALUE;
}
node=node.next;
}
return false;
}
三、最小栈
/** * 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 * push(x) -- 将元素 x 推入栈中。 * pop() -- 删除栈顶的元素。 * top() -- 获取栈顶元素。 * getMin() -- 检索栈中的最小元素。 * * 示例: * MinStack minStack = new MinStack(); * minStack.push(-2); * minStack.push(0); * minStack.push(-3); * minStack.getMin(); --> 返回 -3. * minStack.pop(); * minStack.top(); --> 返回 0. * minStack.getMin(); --> 返回 -2. */
实现栈可以使用数组也可以使用链表,很显然使用链表实现简单一些。
这道题的核心在于如何保存最小值,并在插入删除时及时更新
一个很简单的方法便是将当前的最小值存到当前要插入的节点当中,这样删除时也就更新了最小值
MyListNode listNode;
public MinStack() {
}
public void push(int x) {
if (listNode==null){
listNode=new MyListNode(x,x);
}else {
MyListNode myListNode=new MyListNode(x,Math.min(listNode.min,x));
myListNode.next=listNode;
listNode=myListNode;
}
}
public void pop() {
if (listNode!=null){
listNode=listNode.next;
}
}
public int top() {
return listNode.val;
}
public int getMin() {
if (listNode==null){
return Integer.MAX_VALUE;
}else {
return listNode.min;
}
}
class MyListNode {
int val;
int min;
MyListNode next;
MyListNode(int x,int min) {
val = x;
next = null;
this.min=min;
}
}
四、相交链表
/** * 编写一个程序,找到两个单链表相交的起始节点。 * 示例 1: * 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 * 输出:Reference of the node with value = 8 * 输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 * 示例 2: * 输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 * 输出:Reference of the node with value = 2 * 输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 * 示例 3: * 输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 * 输出:null * 输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 * 解释:这两个链表不相交,因此返回 null。 * 注意: * 如果两个链表没有交点,返回 null. * 在返回结果后,两个链表仍须保持原有的结构。 * 可假定整个链表结构中没有循环。 * 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。 */
相交链表特点:一旦两个链表相交,那么从交点到末尾,两个链表的这部分是相同的
问题是如何满足O(n)的时间复杂度和O(1)的内存使用
定义两个指针, 第一轮让两个到达末尾的节点指向另一个链表的头部, 最后如果相遇则为交点(在第一轮移动中恰好抹除了长度差)两个指针等于移动了相同的距离, 有交点就返回, 无交点就是各走了两条指针的长度
public ListNode method1(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
ListNode pA = headA, pB = headB;
while(pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
另一种思路:先求两个链表的长度,让长的那个先走它俩的长度差,然后两个指针一起走,碰到一起就返回,碰不到就返回null
public ListNode method3(ListNode headA, ListNode headB) {
int len1 = getLength(headA), len2 = getLength(headB);
while(len1 > len2) {
headA = headA.next;
len1--;
}
while(len1 < len2) {
headB = headB.next;
len2--;
}
while(headA != headB) {
headA = headA.next;
headB = headB.next;
}
return headA;
}
private int getLength(ListNode head) {
int length = 0;
while(head != null) {
head = head.next;
length++;
}
return length;
}
还有一种很奇妙的做法,其实也是先去除了两个链表的长度差,这种你需要在纸上画一画才会惊觉它的奇妙
public ListNode method2(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode last = headB;
while (last.next != null) {
last = last.next;
}
last.next = headB;
ListNode fast = headA;
ListNode slow = headA;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
slow = headA;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
last.next = null;
return fast;
}
}
last.next = null;
return null;
}