<Init:从零开始的leetcode编程之旅>(2)

题目2:两数相加

题目说明

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

class Solution {
public:
	ListNode* l3 = new ListNode(0);
	ListNode* p = l3;
	bool flag = false;
	ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
		while (l1 && l2) {
			l3->val = l1->val + l2->val;
			if (flag == true) {
				l3->val += 1;
				flag = false;
			}
			if (l3->val >= 10) {
				l3->val %= 10;
				flag = true;
			}
			if (l2->next || l1->next) {
				l3->next = new ListNode(0);
			}
			else if (flag) {
				l3->next = new ListNode(1);
			}
			else {
				l3->next = NULL;
			}
			l3 = l3->next; l1 = l1->next; l2 = l2->next;
		}
		while (l1) {

			l3->val = l1->val;
			if (flag == true) {
				l3->val += 1;
				flag = false;
			}
			if (l3->val >= 10) {
				l3->val %= 10;
				flag = true;
			}
			if (l1->next) {
				l3->next = new ListNode(0);
			}
			else if (flag) {
				l3->next = new ListNode(1);
			}
			else {
				l3->next = NULL;
			}
			l3 = l3->next; l1 = l1->next;

		}
		while (l2) {

			l3->val = l2->val;
			if (flag == true) {
				l3->val += 1;
				flag = false;
			}
			if (l3->val >= 10) {
				l3->val %= 10;
				flag = true;
			}
			if (l2->next) {
				l3->next = new ListNode(0);
			}
			else if (flag) {
				l3->next = new ListNode(1);
			}
			else {
				l3->next = NULL;
			}
			l3 = l3->next; l2 = l2->next;
		}
		
		return p;

	}
};

思路与问题:

用链表实现一个队列,同时做好位数间的进位,没什么好说的,暴力求解,注意末位存在进位的情况

大佬的代码

//思路1其实没啥区别
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = nullptr, *tail = nullptr;
        int carry = 0;
        while (l1 || l2) {
            int n1 = l1 ? l1->val: 0;
            int n2 = l2 ? l2->val: 0;
            int sum = n1 + n2 + carry;
            if (!head) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail->next = new ListNode(sum % 10);
                tail = tail->next;
            }
            carry = sum / 10;
            if (l1) {
                l1 = l1->next;
            }
            if (l2) {
                l2 = l2->next;
            }
        }
        if (carry > 0) {
            tail->next = new ListNode(carry);
        }
        return head;
    }
};
//思路2--递归
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        return dfs(l1, l2, 0);
    }

    ListNode* dfs(ListNode* l, ListNode* r, int i) {
        if (!l && !r && !i) return nullptr;
        int sum = (l ? l->val : 0) + (r ? r->val : 0) + i;
        ListNode* node = new ListNode(sum % 10);
        node->next = dfs(l ? l->next : nullptr, r ? r->next : nullptr, sum / 10);
        return node;
    } 
};

学到许多:

递归思路比较值得借鉴,特别是用递归创建线性表的操作极其漂亮,并且由于单递归同样是O(n)复杂度,且思路往往更加和逻辑,注意加强对单递归的理解和使用。

题目19:删除链表的倒数第N个结点

题目说明

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

思路与问题:

  1. 遍历第一次,记录长度,动态创建数组,将每个结点的指针存入数组,然后查找对应位置
  2. 注意边界问题的处理,比如只有一个元素则可以直接返回
  3. 不采用动态创建,而是创建一个足够大的数组就可以只遍历一次

自解代码

class Solution {
private:
	int length = 0;
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		int cnt = 0;
		ListNode* p = head;
		while (p) {
			length++;
			p = p->next;
		}
		ListNode** lists = new ListNode*[length];
		p = head;
		while (p) {
			lists[cnt++] = p;
			p = p->next;
		}
		if (length == n ) head = head->next;
		else { lists[length - n - 1]->next = lists[length - n]->next; }
		delete[]lists;
		return head;
	}
};

大佬的代码

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode first = dummy;
    ListNode second = dummy;
    // Advances first pointer so that the gap between first and second is n nodes apart
    for (int i = 1; i <= n + 1; i++) {
        first = first.next;
    }
    // Move first to the end, maintaining the gap
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    second.next = second.next.next;
    return dummy.next;
}

学到许多:

我们可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动 n+1 步,而第二个指针将从列表的开头出发。现在,这两个指针被 nn 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 nn 个结点。我们重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。

这个思路本质上和常用的邻接指针pq相似,要打破思维定势。

题目<偶然遇到,非leetcode>:数组元素循环右移

题目说明

任务描述

本关任务:编写一个算法,将数组A中的n个元素A[0]至A[n-1]循环右移k位。

要求算法时间复杂度为O(n),空间复杂度为O(1)

相关知识

为了完成本关任务,你需要掌握:1.一维数组,2.数组的遍历。

编程要求

根据提示,在右侧编辑器填写算法函数,实现将大小为n的数据元素数组a中的元素循环右移k位。要求函数原型为: void ShiftRightCircular(ElemType a[],int n,int k) 其中k的值可为负整数,表示循环左移;ElemType已经在后台测试程序中定义。

测试说明

平台会自动读取输入数据,对你编写的代码进行测试,并输出结果。

测试输入:105;(输入的是n与k的值) 预期输出: 6 7 8 9 10 1 2 3 4 5

测试输入:10-3; 预期输出: 4 5 6 7 8 9 10 1 2 3

代码求解

class Solution{
    public:
void ShiftRightCircular(ElemType* A, int n, int k)
{
	k = k > 0 ? k % 10 : (10 + k) % 10;
	int i = 0, j = n - k - 1;
	for (int t = 0; t < ((n%k==0)?(n/k-1):(n / k)); t++) {
		for (i = t*k, j = n - k ; i < k*(t+1); i++, j++) {
			A[i] = A[i] + A[j];
			A[j] = A[i] - A[j];
			A[i] = A[i] - A[j];
		}
	}
}
}

学到许多

  1. 本题完成并不难,相对困难的是保证就地计算和线性时间复杂度,对于一个新人(我),可能会默认线性复杂度就是一层循环,但是这是显然不对的,如本次是二层循环,第一层n/k次,第二层k次,相乘仍是O(n)
  2. 这其实也是分治算法的基础,理解本题有助于分析理解分治算法
  3. 注意如何实现数据交换的就地运算,除了本题使用到的“加减法”,还有“乘除法”,“位运算法”等等
posted @ 2020-10-11 10:42  Faura_Sol  阅读(344)  评论(0)    收藏  举报
Live2D